esrf

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

#%TITLE% ICE.MAC 
#
#%NAME%
# Macros to control ISG ICEPAP motor controller as a macro motor
# and to configure macro counters to read DRIVERs temperatures.
# Replacing the old "icepap.mac" set of macros.
#
#%CATEGORY% Positioning, Isg
#
#%DESCRIPTION%
#%DL%
#%DT%Configuring macro motors %DD%
# %DL%
#  %DT% 1)
#       You must have an entry in "MOTORS" table for each icepap MASTER
#       %DL%
#         %DT% -
#         The "DEVICE" field must be set to string "icepap"
#         %DT% -
#         The "ADDR" must be set to the MASTER network name.
#         The communication port can optionaly be specified also 
#         (ex: "isgtmp5:5000")
#         %DT% -
#         The "NUM" should be set to 159
#         %DT% -
#         The "TYPE" field must be set to "Macro Motors"
#       %XDL%
#  %DT% 2) 
#       For each axis you must define a motor with:
#       %DL%
#         %DT% -
#         The "Controller" field set to "MAC_MOT"
#         %DT% -
#         The "Unit" field, numbered from 0,
#         must be set to the MOTORS entry.
#         %DT% -
#         The "Module" field, numbered from 0, 
#         must be set to the ICEPAP rack number (seen on
#         the red 7 segments display of the left hand of each rack)
#         %DT% -
#         The "Chan" field, numbered from 1, 
#         must be set to the DRIVER address 
#         NOTE: to configure a linked axis, the "Chan" field must be set
#         to 9 and the "Module" to whatever value.
#         %DT% -
#         Example: "0/1/2" refers to the first icepap controller declared, 
#         the rack having the number "1" on its display,
#         the 2nd DRIVER of this rack
#       %XDL%
# %XDL%
#
#
#%DT%Configuring macro counters to read a DRIVER ENCODER POSITION%DD%
# %DL%
#  %DT% 1)
#       You must have a "SCALER" define with "icepapenc" as "DEVICE" and
#       "Macro Counter" as "TYPE".
#  %DT% 2)
#       You must define a counter with "MAC_CNT" as "Device". 
#       %DL%
#         %DT% -
#         The "Unit" field must be set to the SCALER entry.
#         %DT% -
#         The "Chan" field must be set to the DRIVER address (ex: 5 for
#         the 5th DRIVER of crate 0 or ex: 23 for the 3rd DRIVER of crate 2)
#         %DT% -
#         The "encoder" parameter can be set to one of the following values.
#         Typically this parameter is set in config "custom parameters"
#         screen for a counter. Hint: type "p" to get in.
#         %DL%
#            %DT% -
#            "abs" Read the SSI absolute encoder
#            %DT% -
#            "absuXX" Read the unsigned SSI XX bits absolute encoder
#            %DT% -
#            "inc" Read the incremental encoder  (DEFAULT)
#            %DT% -
#            "inpos" Read the DRIVER front panel input
#            %DT% -
#            "motor" Read the motor phase position
#         %XDL%
#       %XDL%
# %XDL%
#
#
#%DT%Configuring macro counters to read a single DRIVER TEMPERATURE %DD%
# %DL%
#  %DT% 1)
#       You must have a "SCALER" define with "icepapcnt" as "DEVICE" and
#       "Macro Counter" as "TYPE".
#  %DT% 2)
#       You must define a counter with "MAC_CNT" as "Device". 
#       %DL%
#         %DT% -
#         The "Unit" field must be set to the SCALER entry.
#         %DT% -
#         The "Chan" field must be set to the DRIVER address (ex: 5 for
#         the 5th DRIVER of crate 0 or ex: 23 for the 3rd DRIVER of crate 2)
#       %XDL%
# %XDL%
#
#
#%DT%Configuring macro counters to read several DRIVERs  TEMPERATUREs %DD%
# %DL%
#  %DT% 1)
#       You must have a "SCALER" define with "icepapcalc" as "DEVICE" and
#       "Macro Counter" as "TYPE"
#  %DT% 2)
#       For each counter declared:
#       %DL%
#         %DT% -
#         The "Device" field set to "MAC_CNT"
#         %DT% -
#         The "Unit" field must be set to the SCALER entry.
#         %DT% -
#         The "Chan" field is not used.
#         %DT% -
#         The "Misc 1" parameter (type "s" to access it) is the 
#         crate number (ex: 0)
#         %DT% -
#         The "Misc 2" parameter is the type of calculation
#         done on the DRIVERs temperatures of the crate. Possible values are
#         "max", "min", "avg"
#       %XDL%
# %XDL%
#
#
#%DT%Configuring macro counters to read a single DRIVER internal parameter %DD%
# %DL%
#  %DT% 1)
#       You must have an entry in "SCALER" table for each icepap MASTER
#       %DL%
#         %DT% -
#         The "DEVICE" field must be set to string "icepapmeas"
#         %DT% -
#         The "ADDR" must be set to the MASTER network name.
#         The communication port can optionaly be specified also 
#         (ex: "isgtmp5:5000")
#         %DT% -
#         The "NUM" should be set to 159
#         %DT% -
#         The "TYPE" field must be set to "Macro Counter"
#       %XDL%
#  %DT% 2)
#       You must define a counter with "MAC_CNT" as "Device". 
#       %DL%
#         %DT% -
#         The "Unit" field must be set to the SCALER entry.
#         %DT% -
#         The "Chan" field must be set to the DRIVER address (ex: 5 for
#         the 5th DRIVER of crate 0 or ex: 23 for the 3rd DRIVER of crate 2)
#         %DT% -
#         An extra parameter called "cmd" must be set to the command
#         to be used to get the DRIVER internal parameter (ex: "meas vm" 
#         "meas i" etc)
#       %XDL%
# %XDL%
#
#
#%DT%Extra motor_par() parameters %DD%
# %DL%
#  %DT% motor_par(motor,"power")%DD%
#       Return "1" if the power is enabled on the speficied motor
#  %DT% motor_par(motor,"power",1)%DD%
#       Try to enable the the power the speficied motor and return the result
#  %DT% motor_par(motor,"jog_speed")%DD%
#       Returns the current speed if the motor is in never ending motion
#       or returns "0" if it's stop or in normal operation mode.
#  %DT% motor_par(motor,"jog_speed",speed)%DD%
#       Sets the speed for the never ending motion. If the motor is
#       already moving in this mode, its speed will be changed on the fly.
#       If the new speed has not the same sign that the current one, the
#       motor will first deccelerate until stopping and then starts rotating
#       in the other direction with the new speed.
#  %DT% motor_par(motor,"jog_speed",0)%DD%
#       Cancel a never ending motion.
#  %DT% motor_par(motor,"jog_modulo",steps)%DD%
#       Optional parameter, used to set the motor position when canceling
#       never ending motion.
#  %DT% motor_par(motor,"closed_loop")%DD%
#       Returns nonzero if the closed loop is active
#  %DT% motor_par(motor,"closed_loop",1)%DD%
#       Activate or desactivate the closed loop for the specified motor
#  %DT% motor_par(motor,"reset_closed_loop")%DD%
#       Reset the closed loop error (resynchronize axis position with encoder)
#       Returns non null on success.
#  %DT% motor_par(motor,"home_active")%DD%
#       Returns nonzero if logical home signal is active
#  %DT% motor_par(motor,"high_lim_set")%DD%
#       Returns nonzero if positive limitswitch is active
#  %DT% motor_par(motor,"low_lim_set")%DD%
#       Returns nonzero if negative limitswitch is active
#  %DT% motor_par(motor,"keepusable",1)%DD%
#       Keep the motor usable even if power could not be switched on during
#       reconfig. Typically this parameter is set in config "additional
#       optional parameters" screen for a motor. Hint: type "p" to get in
# %XDL%
#
#%XDL%
#
#%END%
#



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

constant ICEPAP_LOCKFILE  "/users/blissadm/local/spec/userconf/icepap_lock"
constant ICEPAP_TIMEOUT   3

global ICEPAP_STOPCODE[]
ICEPAP_STOPCODE[0] = "last motion stopped normally"
ICEPAP_STOPCODE[1] = "STOP signal received"
ICEPAP_STOPCODE[2] = "ABORT signal received"
ICEPAP_STOPCODE[3] = "Limit+ activated"
ICEPAP_STOPCODE[4] = "Limit- activated"
ICEPAP_STOPCODE[6] = "axis disabled"
ICEPAP_STOPCODE[8] = "internal failure"
ICEPAP_STOPCODE[9] = "motion failure"
ICEPAP_STOPCODE[10]= "power overload"
ICEPAP_STOPCODE[11]= "driver overheating"
ICEPAP_STOPCODE[12]= "close loop error"
ICEPAP_STOPCODE[13]= "control encoder error"
ICEPAP_STOPCODE[15]= "external abort"


#%IU% [personal msg]
#%MDESC%
# Switch on or off the print of debug messages
#
def icepapdebug '{
 global ICEPAP[]

 if(ICEPAP["debug"]) {
  rdef icepap__debug \'#\$#\'
  print "ICEPAP debug mode is OFF"
  ICEPAP["debug"]=0
 } else { 
  rdef icepap__debug \'print "ICEPAP:" \'
  print "ICEPAP debug mode is ON"
  ICEPAP["debug"]=1
 }
}'


#%UU% [on|off]
#%MDESC%
# Switch on (default) brief messages on motors powering error
# during the reconfig.
#
def icepap_brief_messages '{
 global ICEPAP[]
 local  todo arg

 todo = 1
 if($# == 1) {
   arg = "$1"
   if((arg == "on") || (arg == "ON")) {
     todo = 1
   } else if((arg == "off") || (arg == "OFF")) {
     todo = 0
   } else {
     p "Usage: $0 [on|off]"
     exit
   }
 }

 printf("ICEPAP error messages will be: %s", (todo?"BRIEF":"NORMAL"))
 ICEPAP["brief_messages"] = todo
}'


#%IU%
#%MDESC%
# Reset socket communication and MASTER FIFOs
#
def icepap_fiforst(dev) '{
 _icepap_wr(dev,"","_FIFORST")
}'


#%IU%
#%MDESC%
# Called on <Ctrl-C>
#
def icepap_cleanup(dev) '{
  # give time to sockets
  sleep(.02)
  # very dangerous with multiple clients
  # icepap_fiforst(dev)
  sock_par(dev,"flush")
}'


#%IU%
#%MDESC%
# Needed to initialize debug print out macro the first time the 
# file is loaded.
def icepapdebug_init '{

 if(!(whatis("ICEPAP") & 0x05000000)) {
  global ICEPAP[]
  ICEPAP["debug"]=1
  icepapdebug
 }
}'
icepapdebug_init



#%IU%(string, case)
#%MDESC%
# Convert to lower (case==1) or to upper (case==0) case the string passed
#
def icepap__tocase(str, case) '{
  string array str_a[length(str)]
  local l 

  str_a = str
  l = length(str)-1
  for(;l>=0;l--) {
   if(case)
    str_a[l]=str_a[l]+(str_a[l]>=asc("A") && str_a[l]<=asc("Z"))*0x20;
   else
    str_a[l]=str_a[l]-(str_a[l]>=asc("a") && str_a[l]<=asc("z"))*0x20;
  }
  return(sprintf("%s", str_a))
}'


#%IU%(string)
#%MDESC%
# Return the lower string of the string passed
#
def icepap__tolower(str) '{return((str!="")?icepap__tocase(str, 1):"")}'


#%IU%(string)
#%MDESC%
# Return the upper string of the string passed
#
def icepap__toupper(str) '{return((str!="")?icepap__tocase(str, 0):"")}'



#%IU%(string)
#%MDESC%
#
def icepap_trim(str) '{
  local  b l 

  l = length(str)
  if(l == 0) {
     return ""
  }

  string array str_a[l]
  str_a = str
  l = l-1
  for(;(l>=0)&&(str_a[l]==0x20);l--);
  for(b=0;(b<l)&&(str_a[b]==0x20);b++);

  return(substr(str,b+1,(l-b)+1))
}'

#%IU%(x)
#%MDESC%
# Return a well rounded integer as opposed to int() which always
# round down
#
def icepap_round(x) '{ if (x>=0) return int(x+0.5); else return int(x-0.5) }'



#%IU%(ans)
#%MDESC%
# Check if the answer given contains an ERROR and if yes
# request the error message
#
def icepap_chkerr(dev,ans) '{
   if(index(ans,"ERROR") == 1) {
    ans= _icepap_wrrd(dev,0,"?ERR 1")
    print ans;
   }
}'





#%IU%
#%MDESC%
#
def icepap_config(num,type,p1,p2,p3) '{
 global ICEPAP[]
 global ICEPAP_CAT[]
 local  dev
 local  i j
 local  ans
 local  hrev lrev
 local  n tt[]
 local  m aa[]
 local  mne
 local  silent
 


 # p1==controller index p2==number of motors supported
 if(type=="ctrl") {
  silent = 1

  icepap__debug "config(): new controller"
  if (p1 == 0) { for(i in ICEPAP) { delete ICEPAP[i] } }
  ICEPAP["dev"]=""

  # get the MASTER
  dev=icepap_ADDR
  if(dev=="") {
    _icepap_err
    printf("missing ADDR field\n")
    return 
  }
  if(index(dev,":") == 0) { dev=sprintf("%s:5000",dev) }
  ICEPAP["dev"][p1]=dev
  ICEPAP["dev"]=dev

  # if the MASTER is switched off, disable all the associated motors
  ans=_icepap_query(dev,0,"?ID", silent)
  if(ans == "") { return ".error." }

  # get the system version
  hrev = 0
  lrev = 0
  ICEPAP[dev]["hrev"]=0
  ICEPAP[dev]["lrev"]=0
  ans=_icepap_query(dev,"","?VER")
  if(sscanf(ans,"%d.%d",hrev,lrev) == 2) {
    ICEPAP[dev]["hrev"]=hrev
    ICEPAP[dev]["lrev"]=lrev
  }
  
  # try to guess if the firmware supports group motions
  ICEPAP[dev]["groups"]=(((hrev>=1)&&(lrev >= 22)) || (hrev>=2))?"YES":"NO"

  # try to guess if the firmware supports linked axis
  ans=_icepap_query(dev,"","?LINKED",silent)
  n  =split(ans,tt,"\r\n")
  ICEPAP[dev]["linked"]=(!index(ans,"ERROR"))?"YES":"NO"

  # get linked axis configured if any (expecting a multilines answer)
  if((ICEPAP[dev]["linked"] == "YES") && (n>=3)) {
   ICEPAP[dev]["linked_list"]=""

   for(i=0;i<n;i++) {
    if(index(tt[i],"\$")) { 
      continue 
    }

    m   = split(tt[i],aa)
    mne = (m>0)?aa[0]:"???"

    if(m<1) {
      _icepap_err
      printf("bad configured linked axis \"%s\" on \"%s\"\n", \
        mne,dev)
      continue
    }
 
    ICEPAP[dev]["linked_list"]=ICEPAP[dev]["linked_list"] mne " "
    ICEPAP[mne]["linked_addr"]=""
    for(j=1;j<m;j++) {
      ICEPAP[mne]["linked_addr"]=ICEPAP[mne]["linked_addr"] aa[j] " "
    }
    ICEPAP[mne]["linked_addr"]=icepap_trim(ICEPAP[mne]["linked_addr"])
    ICEPAP[mne]["linked"]     ="YES"
   }

   ICEPAP[dev]["linked_list"]=icepap_trim(ICEPAP[dev]["linked_list"])
   
  }


 }


 # p1==unit p2==module p3==channel
 if(type=="mot") {
  local mne
  local dev
  local addr
  local i
  local ret
  local addr
  local silent
  

  # get the motor mnemonic string
  mne=motor_mne(num)

  # get the MASTER
  ICEPAP[mne]["dev"]=""
  dev=motor_par(num,"address")
  if(dev==0) { return }
  if(index(dev,":") == 0) { dev=sprintf("%s:5000",dev) }
  ICEPAP[mne]["dev"]=dev

  # initialize global settings
  ICEPAP[mne]["addr"]=p2*10+p3
  addr=ICEPAP[mne]["addr"]
  ICEPAP[mne]["jog_velocity"]=0
  ICEPAP[mne]["stop_code"]=0
  

  # check if it is linked axis (invalid addresses:9, 19, etc)
  if((addr%10)==9 || addr==0) {
   if(ICEPAP[mne]["linked"] != "YES") {
    _icepap_err
    printf("unusable motor: \"%s\"\n",mne)
    printf("Hint: change address in config or configure linked axe\n")
    motor_par(num,"disable",1)
    return ".error."
   }

   # for a linked axis the mnemonic is used as identification for the DSP
   ICEPAP[mne]["addr"]=mne

   # update globals concerning this linked axis
   if(_linkedstat(num)) {
    motor_par(num,"disable",1)
    return ".error."
   }

   # switch on, at once, the power on all concerned motors
   ans=_icepap_query(dev,"#",sprintf("POWER ON %s", mne))
   if(index(ans,"ERROR"))
   {
    _icepap_err
    printf("unusable motor: \"%s\"  ",mne)
    if(ICEPAP["brief_messages"]) {
     printf("Hint: use \"icepap_diagnostic %s\"\n", mne)
    } else {
     printf("\n")
     _icepap_print_info(dev,addr,mne)
    }
    motor_par(num,"disable",1)
    return ".error."
   }

   # the afterward commands are not implemented in DSP for linked axis:
   #   ?ACTIVE
   #   ?CFG ACTIVE
   #   ?PCLOOP
   return
  } 


  # check if it is menber of a linked axis
  if(ICEPAP[dev]["linked"] == "YES") {
   silent = 1
   ans=_icepap_query(dev,addr,"?CFG LNKNAME",silent)
   if(index(ans,"ERROR"))
   {
    _icepap_err
    printf("unusable motor: \"%s\"  ",mne)
    if(ICEPAP["brief_messages"]) {
     printf("Hint: use \"icepap_diagnostic %s\"\n", mne)
    } else {
     printf("\n")
     _icepap_print_info(dev,addr,mne)
    }
    motor_par(num,"disable",1)
    return ".error."
   }
   n=split(ans,tt)
   ICEPAP[mne]["linked_member"] = (n>1)?"YES":"NO"
   ICEPAP[mne]["linked_name"]   = tt[1]

   if(n>1) {
    printf("ICEPAP WARNING: disabling \"%s\" used for linked axis \"%s\"\n",\
           mne, tt[1])
    motor_par(num,"disable",1)
   
    # all the powering sequence is done the linked axis, so give up here
    return
   }
  }



  # check if the DRIVER is configured and active 
  ans=_icepap_query(dev,addr,"?ACTIVE")
  if(ans != "NO") {
   if (ans != "YES")
   {
    _icepap_err
    printf("unusable motor: \"%s\"\n",mne)
    motor_par(num,"disable",1)
    return ".error."
   }
  }
  else
  {
   ans=_icepap_query(dev,addr,"?CFG ACTIVE")
   if(ans != "ACTIVE YES") {
    _icepap_err
    printf("not configured active driver for motor: \"%s\"\n",mne)
    printf("Hint: use \"icepapcms\" to activate this driver\n")
   } else {
    _icepap_err
    printf("not active driver for motor: \"%s\"\n",mne)
    printf("Hint: probably a hardware problem, crate off/on may help\n")
   }
   motor_par(num,"disable",1)
   return ".error."
  }


  # check if the DSP will handle a closed loop 
  ICEPAP[mne]["pcloop"]="OFF"
  ans=_icepap_query(dev,addr,"?PCLOOP")
  if(index(ans,"ON")) { ICEPAP[mne]["pcloop"]="ON" }

  # check if the DC dead band and settling time are well set in the config
  if(ICEPAP[mne]["pcloop"]=="ON")
  {
   # NOTE 13Dec29: this interdiction could be discuessed, perhaps the user
   # wants to know the current axis position even when there is closed loop
   if(motor_par(num,"mode") == "maxe_e")
   {
    _icepap_err
    printf("closed loop incompatible with \"maxe_e\" for motor: \"%s\"\n",mne)
    printf("Hint: use remove motor parameter \"mode\"\n")
    return ".error." 
   }
   if(motor_par(num,"dc_settle_time") != 0)
   {
    printf("ICEPAP WARNING: settling handled by IcePAP for motor: \"%s\"\n",mne)
    printf("ICEPAP WARNING: the \"DC settle time\" should be set to 0\n")
   }
   if(motor_par(num,"dc_dead_band") != 1)
   {
    printf("ICEPAP WARNING: settling handled by IcePAP for motor: \"%s\"\n",mne)
    printf("ICEPAP WARNING: the \"DC dead band\"   should be set to 1\n")
   }
  }

  
  # switch on the power on the motor, chaud devant
  silent = 1
  ans=_icepap_query(dev,"#" addr,"POWER ON", silent)
  if(index(ans,"ERROR") && motor_par(mne, "keepusable") != 1)
  {
   _icepap_err
   printf("unusable motor: \"%s\"  ",mne)
   if(ICEPAP["brief_messages"]) {
    printf("Hint: use \"icepap_diagnostic %s\"\n", mne)
   } else {
    printf("\n")
    _icepap_print_info(dev,addr,mne)
   }
   motor_par(num,"disable",1)
   return ".error."
  }

  if(motor_par(num,"mode") == "maxe_e") 
  {
   if(ICEPAP[dev]["hrev"]<2) {
     _icepap_err
     printf("can not use \"maxe_e\" mode for motor: \"%s\"\n",mne)
     printf("Hint: upgrade embedded firmware to version higher than 2.0\n")
     return ".error."
   }
   printf("ICEPAP WARNING: using \"maxe_e\" mode for motor: \"%s\"\n",mne)
  }

 }
}'





#%IU%
#%MDESC%
#
def icepap_par(num,key,todo,p1) '{
 global ICEPAP[]
 local  mne
 local  ans
 local  sta
 local  dev

 # get the motor mnemonic string
 mne=motor_mne(num)

 # get the MASTER
 if(!("dev" in ICEPAP[mne])) { return }
 dev=ICEPAP[mne]["dev"]
 if(dev=="") { return }

 # get the address
 if(!("addr" in ICEPAP[mne])) return;
 addr=ICEPAP[mne]["addr"]

 # return new motor_par() argins handled 
 if (key == "?" && todo == "get") {
  return("power, jog_speed, jog_modulo, closed_loop, reset_closed_loop")
 }


 #
 #
 #
 if (key == "power") {
  
  # Change the power on the motor, chaud devant
  if(todo == "set") {
   if(ICEPAP[mne]["linked"] == "YES") {
    ans=_icepap_query(dev,"#",sprintf("POWER %s %s",(p1==1?"ON":"OFF"),addr))
   } else {
    ans=_icepap_query(dev,"#" addr,sprintf("POWER %s",(p1==1?"ON":"OFF")))
   }

   if(index(ans,"ERROR"))
   {
    _icepap_print_info(dev,addr,mne)
    _icepap_err
    printf("unable to power motor: \"%s\"\n",mne)
    return 
   }

  }

  # Return the current state in any case
  if(ICEPAP[mne]["linked"] == "YES") {
   ans=_icepap_query(dev,"",sprintf("?POWER %s",addr))
  } else {
   ans=_icepap_query(dev,addr,"?POWER")
  }
  return(index(ans,"ON")?1:0)
 }



 #
 #
 #
 if (key == "jog_modulo") {
  if(todo == "set") {
   motor_par(num, "jog_modulo_stps", p1, "add")
  }
  return(motor_par(num, "jog_modulo_stps"))
 }


 #
 #
 #
 if (key == "jog_speed") {

  if(ICEPAP[mne]["linked"] == "YES") {
   _icepap_err
   printf("JOG motion no implemented for linked axis\n")
   return 
  }

  # Change or start a JOG motion
  if(todo == "set") 
  {
   local vel
   local acc
   local cmd

   # Just in case the motor had never been moved previously
   acc = motor_par(num,"acceleration")
   icepap_cmd(num,"acceleration",acc)
 
   # If the rotation sense changes, we have to stop first the motor
   # NOTE MP 15Jul13: with firmware >=2.0 this is no more needed
   if(((ICEPAP[mne]["jog_velocity"] * p1) <0) && (ICEPAP[dev]["hrev"]<2))
   {
    # Stop the motion
    ans=_icepap_query(dev,"#" addr,"STOP")
    if(index(ans,"ERROR"))
    {
     _icepap_err
     printf("unable to stop JOG on motor: \"%s\"\n",mne)
     return 
    }

    # Wait for the end of the motion
    while(icepap_cmd(num,"get_status") == 0x02) sleep(.01);
  
    # Give some time to DRIVER  to refresh its status
    sleep(0.1)
   }

   # Treat the special case of null velocity
   # NOTE MP 15Jul13: with firmware >=2.0 a "JOG 0" will block the motor
   # in a never ending loop because the status shows an ongoing motion at
   # null velocity.
   if(p1 == 0) {
    cmd = "STOP"
   } else {
    cmd = sprintf("JOG %d",p1)
   }
   
   # Set the new velocity
   ICEPAP[mne]["jog_velocity"]=p1
   ans=_icepap_query(dev,"#" addr, cmd)
   if(index(ans,"ERROR"))
   {
    _icepap_err
    printf("unable to launch JOG on motor: \"%s\"\n",mne)
    return 
   }
  }

  # Avoid position discrepancy on motion stopped
  if((todo == "set") && (p1 == 0))
  {
   local pos_mm pos_stps
   local period_stps 

   # NOTE MP 21Jun10: wait for the end of motion before reading position
   # can not call wait(0x21) because Spec does not know that we are moving
   while(icepap_cmd(num,"get_status") == 0x02) sleep(.01);

   # Optional period in steps
   period_stps = motor_par(num, "jog_modulo_stps")

   # NOTE MP 26Oct09: on users request, clear the position to avoid
   # huge position values
   pos_mm = 0

   # Set the current axis position with a period
   if(period_stps != 0)
   {
    pos_stps  = icepap_cmd(num,"position") * motor_par(num, "step_size")
    pos_stps %= period_stps
    pos_mm    = pos_stps / motor_par(num, "step_size")
   }

   # change the axis position
   icepap_cmd(num,"set_position", pos_mm)

   # NOTE MP 21Jun10: give time to DSP to update DPM, seen with firmware 1.21
   sleep(.1)
   
   # NOTE: from "help motors": position discrepancies between spec and the
   # motor hardware will be silently resolved in favor of the hardware
   read_motors(0x06)
  }

  # Return the current jog velocity in any case
  # NOTE MP 15Oct09: ?V returns the current instantaneous speed which therefore
  # can not be the target one if the motor is still accelerating.
  # To avoid confusing the user, we return the target one
  if(todo == "set") 
   return(ICEPAP[mne]["jog_velocity"])

  # Return the current instantaneous velocity
  ans=_icepap_query(dev,addr,"?JOG")
  if(index(ans,"ERROR"))
  {
   _icepap_err
   printf("unable to get speed for motor: \"%s\"\n",mne)
   return 
  }
  if(sscanf(ans,"%d",vel) != 1)
  {
   _icepap_err
   printf("unable to read speed for motor: \"%s\"\n",mne)
   return 
  }

  # NOTE MP 15Oct09: ?JOG returns positive only values, little work-arround
  if(ICEPAP[dev]["hrev"]<2) {
   if(ICEPAP[mne]["jog_velocity"]<0) 
    vel *= -1
  }
  
  return(vel)
 }


 #
 #
 #
 if (key == "closed_loop") {
  if(todo == "set") {
   ans=_icepap_query(dev,"#" addr,sprintf("PCLOOP %s",(p1==1?"ON":"OFF")))
   if(index(ans,"ERROR"))
   {
    _icepap_err
    printf("unable to activate closed loop for: \"%s\"\n",mne)
    return 
   }
  }

  # Return the current state of closed loop
  ans=_icepap_query(dev,addr,"?PCLOOP")
  return(index(ans,"ON")?1:0)
 }


 #
 #
 #
 if (key == "reset_closed_loop") {

  # get current encoder position
  ans = _icepap_query(dev, addr, "?POS MEASURE")
  if(index(ans,"ERROR"))
  {
   _icepap_err
   printf("unable to read encoder for: \"%s\"\n",mne)
   return(0)
  }

  # set axis position to same value to remove closed loop discrepancy
  # NOTE MP 21Jul14: the axis command does not work with firmware 2.0
  # the system command must be used instead
  cmd = sprintf("POS %s %s", addr, ans)
  ans = _icepap_query(dev, "#", cmd)
  if(index(ans,"ERROR"))
  {
   _icepap_err
   printf("unable to reset closed loop error on: \"%s\"\n",mne)
   return(0)
  }

  # switch power on (should re-enable the closed loop)
  if(motor_par(num,"power",1) == 0) 
  {
   _icepap_err
   printf("unable to switch power on after closed loop reset on: \"%s\"\n",mne)
   return(0)
  }

  # position discrepancies between spec and the
  # motor hardware will be silently resolved in favor of the hardware
  read_motors(0x06)

  # normal end
  return(1)
 }


 #
 #
 #
 if (key == "home_active") {
  sta = 0
  ans=_icepap_query(dev,"",sprintf("?FSTATUS %s",addr))
  if(sscanf(ans,"%x", sta) != 1) 
  {
   _icepap_err
   printf("unable to get status for: \"%s\"\n",mne)
   return ".error."
  }

  # get home signal status
  return((sta & (1<<20))!=0)
 }


 #
 #
 #
 if (key == "high_lim_set") {
  sta = 0
  ans=_icepap_query(dev,"",sprintf("?FSTATUS %s",addr))
  if(sscanf(ans,"%x", sta) != 1) 
  {
   _icepap_err
   printf("unable to get status for: \"%s\"\n",mne)
   return ".error."
  }

  # get limitswtich status
  return(sta & (1<<18))
 }

 #
 #
 #
 if (key == "low_lim_set") {
  sta = 0
  ans=_icepap_query(dev,"",sprintf("?FSTATUS %s",addr))
  if(sscanf(ans,"%x", sta) != 1) 
  {
   _icepap_err
   printf("unable to get status for: \"%s\"\n",mne)
   return ".error."
  }

  # get limitswtich status
  return(sta & (1<<19))
 }

}'






#%IU%
#%MDESC%
#
def _icepap_prestart_all(dev) '{
 global ICEPAP

 if(ICEPAP[dev]["groups"] != "YES") { return }

 ICEPAP[dev]["tomove"]=""
 ICEPAP[dev]["tostat"]=""
}'

#%IU%
#%MDESC%
#
def _icepap_start_all(dev) '{
 global ICEPAP
 local  aux[]

 if(ICEPAP[dev]["groups"] != "YES") { return }

 # NOTE MP 24Jul12: the start_all should not be call if nothing to move
 # but SPEC version 5.10.02-16 do this
 if(ICEPAP[dev]["tomove"] == "")    { return }

 # NOTE MP 01Feb11: avoid group motion for single axis motion
 cmd=sprintf("MOVE %s %s",\
      (split(ICEPAP[dev]["tostat"],aux)>1)?"GROUP":"",\
      ICEPAP[dev]["tomove"])
 answ = _icepap_query(dev,"#",cmd)
 if (answ != "OK")
 {
  _icepap_err
  printf("unable to start grouped motion\n")
  return ".error."
 }

}'


#%IU%
#%MDESC%
#
def _icepap_start_one(dev, addr, pos) '{
 global ICEPAP




 if(ICEPAP[dev]["groups"] != "YES") 
 { 
  local cmd

  # NOTE MP 31Jan11: even if multi-axes motion exists before group motion
  # their behavior as drastically changed with group implementation
  # therefore it is better to not use them
  cmd=sprintf("MOVE %d",pos)
  addr = "#" addr
  answ = _icepap_query(dev,addr,cmd)
  if (answ != "OK")
  {
   _icepap_err
   printf("unable to start motion for: \"%s\"\n",mne)
   return ".error."
  }

  return
 }
  
 ICEPAP[dev]["tomove"] = sprintf("%s %s %d",ICEPAP[dev]["tomove"],addr,pos)
 ICEPAP[dev]["tostat"] = sprintf("%s %s",   ICEPAP[dev]["tostat"],addr)

}'



# Allows passing arguments (mandatory to avoid SPEC syntax error
# if the argin is not used)
# To use it: cdef("user_icepap_prestart_one","mymacro(\$1)","_gilles")
cdef("user_icepap_prestart_one", "#$*\n", "_icepap")
cdef("user_icepap_prestart_all", "#$*\n", "_icepap")
cdef("user_icepap_abort_one",    "#$*\n", "_icepap")
cdef("user_icepap_abort_all",    "#$*\n", "_icepap")
cdef("user_icepap_postmove_one", "#$*\n", "_icepap")


#%IU%
#%MDESC%
# 
def icepap_cmd(num,key,p1,p2) '{
 global ICEPAP[]
 local  dev
 local  mne
 local  addr




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

  dev = icepap_ADDR
  if(index(dev,":") == 0) { dev=sprintf("%s:5000",dev) }

  if ( key == "prestart_all" ) { 
   user_icepap_prestart_all dev
   return(_icepap_prestart_all(dev))
  }

  if ( key == "start_all" )    { 
   return(_icepap_start_all(dev))    
  }

  if ( key == "abort_all" ) { 
   user_icepap_abort_all dev
  }

  return
 }





 # get the motor mnemonic string
 mne=motor_mne(num)

 # get the MASTER
 if(!("dev" in ICEPAP[mne])) { return }
 dev=ICEPAP[mne]["dev"]
 if(dev=="") { return }

 # NOTE MP 2Aug05: when starting from fresh, SPEC calls 
 # icepap_cmd("position") before calling icepap_config() and 
 # therefore nothing is usable at that moment 
 if(!("addr" in ICEPAP[mne])) return;
 addr=ICEPAP[mne]["addr"]


 #
 # return the current motor position in mm or deg
 #
 if (key == "position") {
  local pos 
  local pos_str
  local ans

  # In MAXE_E like mode, return the encoder position instead of the indexer one
  if(motor_par(num,"mode") == "maxe_e") {
    ans = _icepap_query(dev, addr, "?POS MEASURE")
  } else {
    ans = _icepap_query(dev, "", sprintf("?FPOS %s", addr))
  }
  

  # Request the position from DPM not from the DRIVER
  if(sscanf(ans,"%d",pos) != 1) {
   _icepap_err
   printf("unable to get position for: \"%s\"\n",mne)
   return ".error."
  }
  pos_str = sprintf("%.15g", pos / motor_par(num,"step_size"))
  icepap__debug sprintf("\"%s\": pos      %s",mne,pos_str)
  return(pos_str)
 }


 #
 # start a motion (p1==abs pos, p2==rel pos, with pos in mm or deg)
 #
 if (key == "start_one") {
  local pos 
  local cmd
  local v0
  local vsr
  local a0
  local ans
  local incrpos 
  local indeepshit
  local poubelle
  local maxloops


  # minimum check that motion is possible
  sta = 0
  ans=_icepap_query(dev,"",sprintf("?FSTATUS %s",addr))
  if(sscanf(ans,"%x", sta) != 1) 
  {
   _icepap_err
   printf("unable to get status for: \"%s\"\n",mne)
   return ".error."
  }
  # get ready status
  if(!(sta & (1<<9)))
  {
   local msg

   msg = "(used as shutter??)"
   _icepap_err
   printf("driver is not ready for: \"%s\" %s\n", \
     mne, motor_par(mne, "shutter_mode")?msg:"")

   # temporary: for debug purpose only
   _icepap_print_info(dev,addr,mne)
   return ".error."
  }

  

  


  pos=icepap_round(p1*motor_par(num,"step_size"))
  ICEPAP[mne]["target_steps"]=pos


  # Erase stop code condition to treat it once
  ICEPAP[mne]["stop_code"]=0


  if(motor_par(num,"mode") == "maxe_e") {
   _icepap_wr(dev, addr, "ESYNC")
  }

  return(_icepap_start_one(dev,addr,pos))
 }




 #
 # Check if the driver is powered and ready to use
 #
 if (key == "prestart_one") {
   ICEPAP[mne]["wasmoving"] = 1
   user_icepap_prestart_one num
 }


 #
 # return the current motor status
 #
 if (key == "get_status") {
  local ret
  local ans
  local cms
  local sta sta_neg sta_pos


  ret = 0
  sta = 0
  ans=_icepap_query(dev,"",sprintf("?FSTATUS %s",addr))
  if(sscanf(ans,"%x", sta) != 1) 
  {
   _icepap_err
   printf("unable to get status for: \"%s\"\n",mne)
   return ".error."
  }


  # get moving status
  #
  # NOTE MP 07Jun10: check MOVING and SETTLING status bits
  #   -the MOVING   (10) goes down at the end of the trajectory
  #   -the SETTLING (11) goes down at the end of close loop algorythm
  #
  # NOTE MP 21Jun10: during a HOMING sequence, several motions take place,
  # therefore the MOVING/SETTLING pair can be unset. But the READY remains
  # unset during all the HOMING sequence. To distinguish a non READY axis
  # due to error condition from moving condition, the POWER bit 
  # must also be checked
  #   -the READY    (09) goes down on error or moving
  #   -the POWERON  (23) goes down on error
  #
  if((sta & (1<<10)) || (sta & (1<<11))) ret |= 0x02;
  else if(!(sta & (1<<9)) && (sta & (1<<23))) ret |= 0x02;

  # get limitswitches status
  if(sta & (1<<19)) ret |= 0x04;
  if(sta & (1<<18)) ret |= 0x08;


  # check stop code if not moving, of course
  #
  if(!(ret & 0x02)) {
    _icepap_stopcode(num, sta)
    _icepap_postmove(num)
  }

  # NOTE MP 18May17: SPEC standard macros, on <Ctrl-C>, call "sync" 
  # which detects a shutter motor as not ready and stops it and that 
  # gets the motor out of shutter mode. To avoid this, SPEC must
  # believe that the motor is ready.
  # 
  if(ret & 0x02) {
   if(motor_par(num, "shutter_mode")) {
    return(0)
   }
  }

  return(ret)
 }

 #
 # verify if settling is OK
 #
 if (key =="diff_position") {

  # the settling is handled by the DSP
  if(ICEPAP[mne]["pcloop"]=="ON")
  {
   local ans

   ans=_icepap_query(dev,"",sprintf("?FSTATUS %s",addr))
   if(sscanf(ans,"%x", sta) !=1) {
    _icepap_err
    printf("unable to get status for: \"%s\"\n",mne)
    return(0)
   }

   # by default the motion is finished
   ret=0

   # get settling bit
   if(sta & (1<<11)) { ret=motor_par(num,"dc_dead_band") }

   return ret
  }

  # the settling is handled by SPEC
  if(ICEPAP[mne]["pcloop"]=="OFF")
  {
   local pos 
   local ans
   local poubelle


   ans= _icepap_query(dev,"",sprintf("?FPOS %s",addr))
   if(sscanf(ans,"%f%s",pos,poubelle) != 1) {
    _icepap_err
    printf("unable to get position for: \"%s\"\n",mne)
    return ".error."
   }
  
   # must return the position delta in steps
   return (pos-ICEPAP[mne]["target_steps"])
  }
  
 }


 #
 # stop a single motor
 #
 if (key == "abort_one") {
  local answ

  cmd=sprintf("STOP %s",addr)
  answ = _icepap_query(dev,"#",cmd)
  if (answ != "OK") {
   _icepap_wr(dev,"","STOP")
  }

  user_icepap_abort_one num

  return
 }



 #
 # set position (p1=mm)
 #
 if (key == "set_position") {
  local fpos tbeg

  ans  = _icepap_query(dev,"#",\
      sprintf("POS %s %d", addr, icepap_round(p1*motor_par(num,"step_size"))))
  if(index(ans,"ERROR"))
  {
   _icepap_err
   printf("unable set position for: \"%s\"\n",mne)
   return ".error."
  }

  # give time to the DPM update from concerned DRIVER
  # sleep(.1) 
  tbeg   = time()
  while(((time()-tbeg) < .3)) {
   sscanf(icepap_cmd(num,"position"),"%.15g",fpos)
   if(fabs(fpos-p1) < 1e-3) { break }
  }


  return
 }

 #
 # set acceleration (p1=ms p2=steps/sec^2)
 #
 if (key == "acceleration") {
  ans  = _icepap_query(dev,"#",sprintf("ACCTIME %s %f",addr,p1/1000))
  if(index(ans,"ERROR"))
  {
   _icepap_err
   printf("unable set acceleration for: \"%s\"\n",mne)
   return ".error."
  }
 }


 #
 # set base_rate (p1=rate in Hz)
 #
 if (key == "base_rate") {
  return
 }


 #
 # set slew_rate (p1=rate in Hz)
 #
 if (key == "slew_rate") {
  ans  = _icepap_query(dev,"#",sprintf("VELOCITY %s %d",addr,p1))
  if(index(ans,"ERROR"))
  {
   _icepap_err
   printf("unable set velocity for: \"%s\"\n",mne)
   return ".error."
  }
 }



 #
 # initiate a home or limit search (p1=string with what to search)
 #
 if (key == "search") {
  local lim
  local cmd


  if(ICEPAP[mne]["linked"]=="YES") {
   _icepap_err
   printf("limit search not implemented for linked axis\n")
   return ".error."
  }


  if(p1=="lim+")           { cmd = "SRCH" ; lim="LIM+" }
  else if(p1=="lim-")      { cmd = "SRCH" ; lim="LIM-" }
  else if(p1=="lim+edge")  { cmd = "SRCH" ; lim="LIM+" }
  else if(p1=="lim-edge")  { cmd = "SRCH" ; lim="LIM-" }
  else if(p1=="home+")     { cmd = "HOME" ; lim="+1"   }
  else if(p1=="home-")     { cmd = "HOME" ; lim="-1"   }
  else 
  {
   _icepap_err
   printf("un-supported home or limit search\n")
   return ".error."
  }

  # If already at limit, do nothing
  #
  # NOTE MP 10Nov08:  the default behavior of "LIM+" command is to look for
  # a transition of positive limitswitch (i.e. the DRIVER will automatically
  # change the motor motion sense if the limitswitch is already active)
  # This behavior can be used using "lim+edge" argin.
  #
  if(p1=="lim+") { if(icepap_cmd(num,"get_status") == 0x08) { return } }
  if(p1=="lim-") { if(icepap_cmd(num,"get_status") == 0x04) { return } }

  
  # NOTE MP 10Nov08: the local variables are erased by the recursive call
  # to icepap_cmd("get_status"). Bug fixed in release 5.08.03-3
  mne = motor_mne(num)
  dev = ICEPAP[mne]["dev"]
  addr= ICEPAP[mne]["addr"]
 

  addr = "#" addr
  ans  = _icepap_query(dev,addr,sprintf("%s %s",cmd,lim))
  if(index(ans,"ERROR"))
  {
   _icepap_err
   printf("unable to start limit search\n")
   return ".error."
  }

  # NOTE MP 07Jun10:  do a polling on ?SRCHSTAT ?HOMESTAT to have
  # the found/notfound information from the DSP directly and to be
  # able to do some usage of the SRCHPOS and HOMEPOS
  #
  # from here, spec will do the polling on status until the
  # end of the motion. 
  return
 }

}'


#%UU% motor
#%MDESC%
# Print out information on the specified ICEPAP motor
#
def icepap_diagnostic '{
   global ICEPAP[]
   local  mne
   local  num
   local  dev addr

   if($# == 0) {
     p "Usage: $0 motor"
     exit
   }

   mne = "$1"
   if(_icepap_ismot(mne)) {
      exit
   }

   dev  = ICEPAP[mne]["dev"]
   addr = ICEPAP[mne]["addr"]
   _icepap_print_info(dev, addr, mne)
}'


#%IU%(dev,addr,mne)
#%MDESC%
#
def _icepap_print_info(dev,addr,mne) '{
   local n tt[]


   # check if linked axe or linked one
   if(ICEPAP[mne]["linked"] == "YES") {
    n=split(ICEPAP[mne]["linked_addr"],tt)
    for(;n;n--) {
     _icepap_print_info_driver(dev,tt[n-1],mne)
    }
   } else {
    _icepap_print_info_driver(dev,addr,mne)
   }

}'


#%IU%(dev,addr)
#%MDESC%
# TODO: unify with icepap_info macro
#
def _icepap_print_info_driver(dev,addr,mne) '{
   local num
   local tab
   tab="\t|"

   print "\t---------------------------------------------------------"
   num = motor_num(mne)
   print tab"MOTOR  : ",mne
   print tab"SYSTEM : ",motor_par(num,"address")
   print tab"DRIVER : ",sprintf("%02d",addr)
   print tab"POWER  : ",_icepap_query(dev,addr,"?power")
   print tab"CLOOP  : ",_icepap_query(dev,addr,"?pcloop")
   print tab"WARNING: ",_icepap_query(dev,addr,"?warning")
   print tab"ALARM  : ",_icepap_query(dev,addr,"?alarm")

   print tab"PWRINFO: "
   _icepap_print_tabbed(tab,_icepap_query(dev,addr,"?isg ?pwrinfo"))
   print "\t---------------------------------------------------------"

}'

def _icepap_print_tabbed(tab,str) '{
  local lines[]
  local i,n

  n=split(str,lines,"\n")
  for(i=0,ret="";i<n;i++) {
      print tab,lines[i]
  }
}'

#%UU% [hostname|motor]
#%MDESC%
# Print out information on the specified ICEPAP system or on an
# ICEPAP motor configured in the current SPEC session.
#
def icepap_info '{
   local todo
   local num

   if($# == 0) {
     p "Usage: $0 [hostname | motor]"
     exit
   }

   # TODO MP : add multiple argins
   todo = "$1"
   if((num=motor_num(todo)) != -1) {
      _icepap_print_info_mot(num)
   } else {
      _icepap_print_info_system(todo)
   }
}'


#%UU% motor  |  hostname  [addr [addr ... ]]
#%MDESC%
# Print out detailed memory information for the specified DRIVERs
# The DRIVER can be specified by their address or by the corresponding 
# motor already configured.
# If only an IcePAP system is given, all its DRIVERs will be print out.
#
def icepap_mem_info '{
   local todo
   local num
   local dev addr 
   local ans i n addrs[]

   if($# == 0) {
     p "Usage: $0 motor  |  hostname [addr [addr ... ]]"
     exit
   }

   todo = "$1"
   if((num=motor_num(todo)) != -1) {
      if(motor_par(num, "device_id") != "icepap") {
        _icepap_err
        printf("Not an IcePAP motor: \"%s\"\n", "$1")
        exit
      }

      n         = 1
      dev       = motor_par(num,"address")
      addrs[0]  = motor_par(num,"module")*10 
      addrs[0] += motor_par(num,"channel")

   } else {

      dev = "$1"
   
      if($# > 1) {
         n   = split("$*", addrs)
         for(i=1;i<n;i++) { addrs[i-1] = addrs[i] }
         n   = n - 1 
      } else {
         ans = _icepap_get_addrs(dev)
         n   = split(ans, addrs)
      }
   }

   for(i=0;i<n;i++) {
     _icepap_mem_driver(dev, addrs[i])
   }
}'


#%IU%(motor)
#%MDESC%
#
def _icepap_print_info_mot(num) '{
   local mne
   local ad sw hn i

   sw  = 15
   mne = motor_mne(num)
   if(!("dev" in ICEPAP[mne])) { 
     p "ERROR: unknown motor"
     return 
   }
   printf("%*s : %s\n",sw,"motor name",mne)

   hn = ICEPAP[mne]["dev"]
   if(i=index(hn,":")) { hn = substr(hn,0,i-1) }
   printf("%*s : %s\n",sw,"system",hn)

   ad = ICEPAP[mne]["addr"]
   printf("%*s : %s\n",sw,"address",ad)

   printf("%*s : %s\n",sw,"power",motor_par(num,"power")?"ON":"OFF")
   printf("%*s : %s\n",sw,"closed loop",motor_par(num,"closed_loop")?"ON":"OFF")
  
}'



#%IU%(hostname)
#%MDESC%
#
def _icepap_print_info_system(dev) '{
   local sw ans silent
   local i nm nums[] ns

   silent = 1


   ans =_icepap_query(dev,0,"?VER", silent)
   if(ans == "") { 
     p "ERROR: unknown or unreachable system"
     return
   }
   sw  = 15
   printf("%*s : %s\n",sw,"system",dev)
   printf("%*s : %s\n",sw,"version",icepap_trim(ans))

   # look for motor from this system configured in the current session
   for(i=0,nm=0,ns="";i<MOTORS;i++) {
      if(!index(motor_par(i,"address"),dev)) { continue }
      nums[nm++] = i
      ns = sprintf("%s%s ",ns,motor_mne(i))
   }
   printf("%*s : %s\n",sw,"motors used",nm?ns:"NONE")
}'


#%UU% [hostname|motor]
#%MDESC%
# Check the firmware version(s) of the specificied ICEPAP system or 
# ICEPAP motor configured in the current SPEC session.
#
def icepap_firmware_check '{
   local todo
   local num

   if($# == 0) {
     p "Usage: $0 [hostname | motor]"
     exit
   }

   todo = "$1"
   if((num=motor_num(todo)) != -1) {
     _icepap_fw_check_mot(num) 
   } else {
     _icepap_fw_check_system(todo) 
   }

}'

#%IU%(motor)
#%MDESC%
#
def _icepap_fw_check_mot(num) '{
   p "NOT IMPLEMENTED YET"
}'




#%IU%(hostname)
#%MDESC%
#
def _icepap_fw_check_system(dev) '{
   local r_saved
   local r_master
   local r_drivers[]
   local n_drivers
   local r_failed_new[]
   local r_failed_old[]
   local s_failed_new
   local s_failed_old
   local n_failed
   local ans
   local addrs
   local i n a 
   local cmp cmp_saved
   local sw



   #
   # Collect information
   #
   printf("collecting data...\r");

   # Get the system revisions
   r_master = _icepap_fw_controller(dev)
   r_saved  = _icepap_fw_saved(dev)

   # Get the list of modules
   ans = _icepap_get_addrs(dev)
   n   = split(ans, addrs)
   for(i=0,n_drivers=0;i<n;i++) {
      a = addrs[i]
      if(a==0) { continue }

      r_drivers[a] = _icepap_fw_driver(dev,a)
      n_drivers++

      if(cmp = _icepap_fw_compare(r_master,r_drivers[a])) {
         if(cmp == 2) { 
             r_failed_new[a] = cmp 
             s_failed_new    = sprintf("%s %s",a,s_failed_new)
             n_failed_new++ 
         }
         if(cmp == 1) { 
             r_failed_old[a] = cmp ; 
             s_failed_old    = sprintf("%s %s",a,s_failed_old)
             n_failed_old++ 
         }
         n_failed++
      }
   }



   #
   # Display results
   #
   sw  = 21
   cmp_saved = _icepap_fw_compare(r_master,r_saved)
   printf("%*s : %s\n",sw,"system version",r_master)
   printf("%*s : %s\n",sw,"check saved",  !cmp_saved?"OK":"FAILED")
   ans = sprintf("check %d drivers",n_drivers)
   printf("%*s : %s\n",sw, ans, !n_failed?"OK":"FAILED")

   if(n_failed) {
      printf("%*s : %s\n",sw,"found mismatch on", \
         sprintf("%d driver%s",n_failed,n_failed==1?"":"s"))
   }
    
   if(n_failed_old) {
      #printf("%*s : ",sw, "too old drivers")
      #for(a in r_failed_old) { printf("%s ", a) }
      printf("%*s : %s",sw, "too old drivers", s_failed_old)
      printf("\n")
   }
   

   if(n_failed_new) {
      printf("%*s : ",sw, "too modern drivers")
      for(a in r_failed_new) { printf("%s ", a) }
      printf("\n")
   }



   #
   # Actions to take
   #
   if(cmp_saved) {
      p ""
      p "WARNING: firmware saved not up to date, giving up"
      p "HINT   : use the command \"icepap_prog\" with flag \"SAVE\" "
      exit
   }

   if((n_failed_new) && (n_failed_new > (n_drivers/2))) {
      p ""
      p "WARNING: there are too many drivers with too modern firmware,giving up"
      p "HINT   : use the command \"icepap_prog\" with flag \"ALL\" "
      exit
   }

   if(n_failed_old) {
      p ""
      p "WARNING: there are drivers with too old firmware"
      if(yesno("Do you agree to upgrade all of them",1)) {
         # NOTE MP 26Feb13: according to ICEPAP User Manual, the PROG command
         # accepts only on module address, therefore, we have to program
         # one after the other
         for(a in r_failed_old) { 
            printf("\n%*s : %s\n",sw, "upgrading driver",a)
            _icepap_prog(dev,"",a,"FORCE")
         }
      }
   }

   if((n_failed_new) && (n_failed_new < (n_drivers/2))) {
      p ""
      p "WARNING: there are drivers with too modern firmware"
      if(yesno("Do you agree to downgrade all of them",0)) {
         # NOTE MP 26Feb13: according to ICEPAP User Manual, the PROG command
         # accepts only on module address, therefore, we have to program
         # one after the other
         for(a in r_failed_new) { 
            printf("\n%*s : %s\n",sw, "downgrading driver",a)
            _icepap_prog(dev,"",a,"FORCE")
         }
      }
   }
}'


#%IU%(rev1, rev2)
#%MDESC%
# Compare deux revisions, given as strings, and returns:
#  0 if rev1 equal rev2
#  1 if rev1 > rev2
#  2 if rev1 < rev2
# -1 if invalid revision numbers
#
def _icepap_fw_compare(rev1, rev2) '{
  local hrev1 lrev1
  local hrev2 lrev2
  local ret

  if(sscanf(rev1,"%d.%d",hrev1,lrev1) != 2) { return(-1) }
  if(sscanf(rev2,"%d.%d",hrev2,lrev2) != 2) { return(-1) }

  if(hrev1>hrev2) { return(1) }
  if(hrev1<hrev2) { return(2) }
  if(lrev1>lrev2) { return(1) }
  if(lrev1<lrev2) { return(2) }

  return(0)
}'


#%IU%(hostname)
#%MDESC%
# Returns, for the specified system, the firmware version of the CONTROLLER
#
def _icepap_fw_controller(dev) '{
  return(_icepap_fw_driver(dev,0))
}'


#%IU%(hostname, addr)
#%MDESC%
# Returns, for the specified system, the firmware version of the DRIVER
# specified by its address.
#
def _icepap_fw_driver(dev, addr) '{
  local rev
  local ans

  # get the system version
  rev="0.0"
  ans=_icepap_query(dev,addr,"?VER")
  if(index(ans,"ERROR")) { return(rev) }

  rev=icepap_trim(ans)
  return(rev)
}'


#%IU%(hostname, addr)
#%MDESC%
# Print out, for the specified system, detailed memory information of 
# the DRIVER specified by its address.
# Returns non null if error occured.
#
def _icepap_mem_driver(dev, addr) '{
  local ans
  local i n
  local pt sz tp

  # first get the number of blocks (free and allocated)
  ans=_icepap_query(dev,addr,"?MEMORY -1")
  if(sscanf(ans, "%d", n) != 1) {
    _icepap_err
    print "error gathering information"
    return(-1)
  }

  printf("----------------------------------\n")
  printf("| %-31s|\n", sprintf("system: %s  address: %d", dev, addr))
  printf("|--------------------------------|\n")
  printf("| %-10s | %4s | %10s |\n", "block addr", "type", "bytes")
  printf("|--------------------------------|\n")
  for(i=0;i<n;i++) {
    ans=_icepap_query(dev,addr,"?MEMORY 0")
    sscanf(ans, "%s %d", pt, sz)
    if(sz<0) {
      sz = -sz
      tp = "USED"
    } else {
      tp = "FREE"
    }
    printf("| %-10s | %4s | %10d |\n", pt, tp, sz)
  }
  printf("----------------------------------\n\n")
}'

#%IU%(hostname)
#%MDESC%
# Returns, for the specified system, the firmware version saved in flash
#
def _icepap_fw_saved(dev) '{
  local rev
  local ans
  local i n lines[] elts[]

  # get the system version
  rev="0.0"
  ans=_icepap_query(dev,"","?VER SAVED")
  if(index(ans,"ERROR")) { return(rev) }

  n=split(ans,lines,"\n")
  for(i=0;i<n;i++) {
     if(!index(lines[i],"SYSTEM")) { continue }
     split(lines[i],elts,":")
     rev=icepap_trim(elts[1])
     break
  }

  return(rev)
}'

#%UU% [hostname[:port]]
#%MDESC%
# Reset all the racks of the specified ICEPAP MASTER
#
def icepap_reset'{
 global ICEPAP[]
 local dev
 
 if($# < 1) {
  dev=getval("Icepap MASTER network name",ICEPAP["dev"])
 } else {
  dev="$1"
 }

 if(!index(dev,":")) { dev = dev":5000" }
 ICEPAP["dev"]=dev

 _icepap_wr(dev,"","RESET")
}'



#%IU% axis
#%MDESC%
# CONFIG LOCK:
# Add a lock on the specified axis (ex: 0/2)
#
def icepap_lock '{
 local a
 local u c
 local line

 if($# != 1) {
  print "USAGE: $0 axis"
  print "   Ex: $0 0/2"
  exit
 }
 a="$1"
 _icepap_lock(a,1)
}'


#%IU% axis
#%MDESC%
# CONFIG LOCK:
# Remove a lock on the specified axis (ex: 0/2)
#
def icepap_unlock '{
 local a
 local u c
 local line

 if($# != 1) {
  print "USAGE: $0 axis"
  print "   Ex: $0 0/2"
  exit
 }
 a="$1"
 _icepap_lock(a,0)
}'


#%IU%
#%MDESC%
# CONFIG LOCK:
# Check that lock file exist and create empty if not
#
def _icepap_islockfile() '{

 if(file_info(ICEPAP_LOCKFILE, "-w") == 0){
  printf("ICEPAP WARNING: touching \"%s\"\n",ICEPAP_LOCKFILE)
  if(unix(sprintf("touch %s",ICEPAP_LOCKFILE)) != 0) {
   _icepap_err
   print "error creating file "ICEPAP_LOCKFILE
   return(-1)
  }
 }
 close(ICEPAP_LOCKFILE)

 if(getline(ICEPAP_LOCKFILE, "open") < 0){
  _icepap_err
  printf("Can\'t open file \"%s\"\n", ICEPAP_LOCKFILE)
  return(-1)
 } 

 return(0)
}'


#%IU%(axis,lock)
#%MDESC%
# CONFIG LOCK:
# Add or remove (lock=1 or 0) a lock on the specified axis (ex: 0/2)
#
def _icepap_lock(arg,lock) '{
 local a
 local u c
 local line
 local nline
 local found

 if(sscanf(arg,"%d/%d",u,c)!=2) {
  _icepap_err
  print "bad axis syntax (Ex: \"0/2\")"
  return(-1)
 }

 a=sprintf("%d/%d\n",u,c)

 if(_icepap_islockfile() != 0) 
  return(-1);


 line =""
 nline=""
 found=0
 while ((line = getline(ICEPAP_LOCKFILE)) != -1) 
 {
  if(lock)  
  {
   if(line == a) {
    print "AXIS already locked"
    close(ICEPAP_LOCKFILE)
    return(-1)
   }

  } else {

   if(line == a) { 
    printf("ICEPAP : removing lock on axis %s\n",a)
    found=1 
   } else { 
    nline=sprintf("%s%s",nline,line) 
   }
  }

 }

 if(lock) {
  printf("ICEPAP : adding lock on axis %s\n",a)
  fprintf(ICEPAP_LOCKFILE,"%s",a)
  close(ICEPAP_LOCKFILE)
  return(0)
 }

 if(found == 0) {
  _icepap_err
  print "axis was not locked"
  close(ICEPAP_LOCKFILE)
  return(-1)
 }

 unix(sprintf("\\rm -f %s",ICEPAP_LOCKFILE))
 unix(sprintf("touch %s",ICEPAP_LOCKFILE))
 open(ICEPAP_LOCKFILE)
 fprintf(ICEPAP_LOCKFILE,"%s",nline)
 close(ICEPAP_LOCKFILE)
 return(0)
}'


#%IU%(axis)
#%MDESC%
# CONFIG LOCK:
# Return 1 if a lock exist for the specified axis (ex: 0/2)
# Return 0 if there is no lock.
# Return -1 in case of error.
#
def _icepap_islock(arg) '{
 local a
 local u c
 local line
 local nline
 local found

 if(sscanf(arg,"%d/%d",u,c)!=2) {
  _icepap_err
  print "bad axis syntax (Ex: \"0/2\")"
  return(-1)
 }

 a=sprintf("%d/%d\n",u,c)

 if(_icepap_islockfile() != 0) 
  return(-1);

 line =""
 found=0
 while ((line = getline(ICEPAP_LOCKFILE)) != -1) 
 {
  if(line == a) { 
   found=1 
   break
  }
 }

 close(ICEPAP_LOCKFILE)
 return(found)

}'



#
# ----------------------- MACRO COUNTER implementation -----------------------
#

#%IU%(hostname:port, address)
#%MDESC%
#
def _icepapcnt_gettemp(dev,addr) '{
 local ans
 local t

 ans= _icepap_query(dev,addr,"?MEAS T")
 if(sscanf(ans,"%d",t) != 1) {
  _icepap_err
  printf("%s: addr: %s: unable to get temp\n",mne,addr)
  icepap_chkerr(dev,ans)
  icepap_fiforst(dev)
  return(0)
 }
 return(t)
}'


#%IU%
#%MDESC%
#
def icepapcnt_config(num,type,p1,p2,p3) '{
 global ICEPAP_CNT[]
 local mne
 local dev

 if(type=="ctrl") {
  return
 }

 # p1 is the unit
 # p2 is always 0
 # p3 is the channel which is the two digits address
 if(type=="cnt") {
   mne=cnt_mne(num)
   ICEPAP_CNT[mne]["addr"]=p3

   # get the MASTER
   ICEPAP_CNT[mne]["dev"]=""
   dev=counter_par(num,"address")
   if(dev==0) {
    printf("ICEPAPCNT ERROR: missing ADDR field\n")
    return 
   }
   if(index(dev,":") == 0) { dev=sprintf("%s:5000",dev) }
   ICEPAP_CNT[mne]["dev"]=dev
 }
}'

#%IU%
#%MDESC%
#
def icepapcnt_cmd(num,key,p1,p2) '{
 global ICEPAP_CNT[]
 local mne
 local addr
 local dev


 if (key == "counts") {
  mne=cnt_mne(num)
  addr=ICEPAP_CNT[mne]["addr"]

  # get the MASTER
  if(!("dev" in ICEPAP_CNT[mne])) { return }
  dev=ICEPAP_CNT[mne]["dev"]
  if(dev=="") { return }

  S[num]=_icepapcnt_gettemp(dev,addr)
 }
}'




#
# ----------------------- MACRO COUNTER implementation -----------------------
#

#%IU%(<counter_number>, <hostname:port>, <address>)
#%MDESC%
#
def _icepapenc_getpos(counter_num, dev, addr) '{
    local ans
    local t
    local cmd
    local typ

    # "encoder" parameter is set to "inc" by default in config.
    typ = counter_par(counter_num, "encoder")
    cmd = ICEPAP_ENCCMD[typ]
    ans = _icepap_query(dev,addr,sprintf("?ENC %s", cmd))
    if(sscanf(ans, "%lf", t) != 1) {
        _icepap_err
        printf("counter \"%s\": addr: \"%s\": unable to get enc pos\n", \
               cnt_mne(counter_num), addr)
        icepap_chkerr(dev, ans)
        icepap_fiforst(dev)
        return(0)
    }

    # handle specific case of unsigned SSI encoders
    if(typ == "absu") {
        t += ((t<0)?(1<<counter_par(counter_num, "ssi")):0)
    }

    # normal end
    return(t)
}'


#%UU% (<counter_mnemonic>)
#%MDESC%
#    Returns value of encoder associated to macro-counter
# <encoder_mnemonic>.
# This function is similar to the previous one but provided to be
# user-callable by beeing more user-friendly.
def icepapenc_read(counter_mne) '{
    local  _dev  _addr  _res

    if(!("dev" in ICEPAP_ENC[counter_mne])) {
        printf("counter %s: unable to read encoder\n", counter_mne)
        printf("--> check that your icepap macro counter is defined in SPEC config. \n")
        return
    }
    else{
        _dev  = ICEPAP_ENC[counter_mne]["dev"]
        _addr = ICEPAP_ENC[counter_mne]["addr"]

        _res = _icepapenc_getpos(cnt_num(counter_mne), _dev, _addr)
        return _res
    }
}'


#%IU%(counter, hostname:port, address, pos)
#%MDESC%
#
def _icepapenc_setpos(num,dev,addr,pos) '{
 local ans
 local t
 local cmd 
 local typ
 
 typ = counter_par(num,"encoder")
 if(index(typ,"abs")) {
  _icepap_err
  p "no sense to set the position of an absolute encoder"
  return
 }

 cmd = ICEPAP_ENCCMD[typ]
 ans = _icepap_wrrd(dev,sprintf("#%s",addr),sprintf("ENC %s %d",cmd,pos))
 if(index(ans,"ERROR"))
 {
  _icepap_err
  print ""ans
  return
 }
}'

#%UU% counter position
#%MDESC%
#
def icepap_setenc '{
 if($#!=2) {
  p "Usage: $0 counter position"
  exit
 }

 _icepap_setenc("$1",$2)
}'


#%IU%(counter_mne, position)
#%MDESC%
#
def _icepap_setenc(mne,pos) '{
 local addr
 local dev
 local num

 addr=ICEPAP_ENC[mne]["addr"]
 dev =ICEPAP_ENC[mne]["dev"]

 if(addr == 0) {
  _icepap_err
  p "\""mne"\" not an ICEPAP encoder counter"
  return
 }
 
 num = cnt_num(mne)
 pos = pos * counter_par(num,"scale")
 _icepapenc_setpos(num,dev,addr,pos)
}'



#%IU%
#%MDESC%
#
def icepapenc_config(num,type,p1,p2,p3) '{
 global ICEPAP_ENC[]
 global ICEPAP_ENCCMD[]
 local mne
 local dev

 if(type=="ctrl") {

  ICEPAP_ENCCMD["inc"]   = "ENCIN"
  ICEPAP_ENCCMD["abs"]   = "ABSENC"
  ICEPAP_ENCCMD["absu"]  = "ABSENC"
  ICEPAP_ENCCMD["inpos"] = "INPOS"
  ICEPAP_ENCCMD["motor"] = "MOTOR"
  ICEPAP_ENCCMD["axis"]  = "AXIS"
  ICEPAP_ENCCMD["sync"]  = "SYNC"

  dev = icepapenc_ADDR
  ans=_icepap_query(dev,0,"?ID", silent)
  if(ans == "")
    { return ".error." }
  return
 }

 # p1 is the unit
 # p2 is always 0
 # p3 is the channel which is the two digits address
 if(type=="cnt") {
   mne=cnt_mne(num)
   ICEPAP_ENC[mne]["addr"]=p3

   # get the MASTER
   ICEPAP_ENC[mne]["dev"]=""
   dev=counter_par(num,"address")
   if(dev==0) {
    printf("ICEPAPCNT ERROR: missing ADDR field\n")
    return  ".error."
   }
   if(index(dev,":") == 0) { dev=sprintf("%s:5000",dev) }
   ICEPAP_ENC[mne]["dev"]=dev

   # get the type of encoder to read
   typ = counter_par(num,"encoder")
   if((typ == -1) || (typ == 0)) {
    counter_par(num,"encoder","inc","add")
    typ = "inc"
   }
   # minium check
   if((index(typ,"abs") != 1) && !(typ in ICEPAP_ENCCMD)) {
    _icepap_err
    printf("invalid \"encoder\" parameter for counter \"%s\"", mne)
    return  ".error."
   }

   # handle the specific case of unsigned SSI encoders
   if((typ != "abs") && index(typ,"absu")) {
    local ssi
    if(sscanf(typ,"absu%d",ssi) != 1) {
      _icepap_err
      printf("invalid \"encoder\" parameter \"absu\" for counter \"%s\"", mne)
      return  ".error."
    }
    counter_par(num,"encoder","absu")
    counter_par(num,"ssi",ssi,"add")
    ICEPAP_ENC[mne]["ssi"]=ssi
   }
   # normal end
   ICEPAP_ENC[mne]["typ"]=typ
 }
}'

#%IU%
#%MDESC%
#
def icepapenc_cmd(num,key,p1,p2) '{
 global ICEPAP_ENC[]
 local mne
 local addr
 local dev


 if (num != "..") {
  mne=cnt_mne(num)
  addr=ICEPAP_ENC[mne]["addr"]

  # get the MASTER
  if(!("dev" in ICEPAP_ENC[mne])) { return }
  dev=ICEPAP_ENC[mne]["dev"]
  if(dev=="") { return }
 }

 if (key == "start_one") {
  icepap_cleanup(dev)
 }

 if (key == "counts") {
  return _icepapenc_getpos(num,dev,addr)
 }

 if (key == "halt_one") {
  icepap_cleanup(dev)
 }
}'






#
# ----------------------- MACRO COUNTER implementation -----------------------
#

#%IU%
#%MDESC%
#
def icepapcalc_config(num,typ,p1,p2,p3) '{
 global ICEPAP_CALC[]
 local mne
 local crate
 local type
 local dev

 if(typ=="ctrl") {
  ICEPAP_CALC["min_crate"]=0
  ICEPAP_CALC["max_crate"]=0
  return
 }

 # p1 is the unit
 # p2 is always 0
 # p3 is the channel which is the two digits address
 if(typ=="cnt") {
   mne  =cnt_mne(num)
   crate=counter_par(num,"misc_par_1")
   if((crate<0) || (crate>16)) {
    p "\tERROR: counter "mne" unusable: invalid crate as \"misc_par_1\""
    p "\tHINT:  type \"help local icepap\""
    return
   }
   type =counter_par(num,"misc_par_2")
   if((type!="min") && (type!="max") && (type!="avg")) {
    p "\tERROR: counter "mne" unusable: invalid type as \"misc_par_2\""
    p "\tmust be \"max\" \"min\" or \"avg\""
    p "\tHINT:  type \"help local icepap\""
    return
   }
   ICEPAP_CALC[mne]["crate"]=crate
   ICEPAP_CALC[mne]["type"] =type

   if(crate>ICEPAP_CALC["max_crate"]) { ICEPAP_CALC["max_crate"]=crate }
   if(crate<ICEPAP_CALC["min_crate"]) { ICEPAP_CALC["min_crate"]=crate }

   # get the MASTER
   ICEPAP_CALC["dev"]=""
   dev=counter_par(num,"address")
   if(dev==0) {
    printf("ICEPAPCALC ERROR: missing ADDR field\n")
    return 
   }
   if(index(dev,":") == 0) { dev=sprintf("%s:5000",dev) }
   ICEPAP_CALC["dev"]=dev

 }
}'

#%IU%
#%MDESC%
#
def icepapcalc_cmd(num,key,p1,p2) '{
 global ICEPAP_CALC[]
 local mne
 local addr
 local crate
 local type
 local t j c
 local t_min t_max t_tot t_avg n_tot
 local dev

 if (key == "prestart_all") {
  # get the MASTER
  if(!("dev" in ICEPAP_CALC)) { return }
  dev=ICEPAP_CALC["dev"]
  if(dev=="") { return }

  for(crate=ICEPAP_CALC["min_crate"];crate<=ICEPAP_CALC["max_crate"];crate++) {

   t_max=0
   t_min=0
   t_tot=0
   n_tot=0
   for(j=1;j<=8;j++) {
    addr=sprintf("%d%d",crate,j)
    t=_icepapcnt_gettemp(dev,addr)
    if(!t) { continue }
    if(!t_min)  { t_min=t }
    if(t>t_max) { t_max=t }
    if(t<t_min) { t_min=t }
    t_tot += t
    n_tot++
   }
   t_avg=t_tot/n_tot
   ICEPAP_CALC[crate]["min"]=t_min
   ICEPAP_CALC[crate]["max"]=t_max
   ICEPAP_CALC[crate]["avg"]=t_avg
   ICEPAP_CALC[crate]["n"]  =n_tot
  }
 }

 if (key == "counts") {
  mne   =cnt_mne(num)
  type  =ICEPAP_CALC[mne]["type"]
  crate =ICEPAP_CALC[mne]["crate"]
  S[num]=ICEPAP_CALC[crate][type]
 }
}'




#
# ----------------------- MACRO COUNTER implementation -----------------------
#


#%IU%
#%MDESC%
#
def icepapmeas_config(num,type,p1,p2,p3) '{
 global ICEPAP_MEAS[]
 local mne
 local dev
 local cmd

 if(type=="ctrl") {
  return
 }

 # p1 is the unit
 # p2 is always 0
 # p3 is the channel which is the two digits address
 if(type=="cnt") {
   mne=cnt_mne(num)
   ICEPAP_MEAS[mne]["addr"]=p3

   # get the MASTER
   ICEPAP_MEAS[mne]["dev"]=""
   dev=counter_par(num,"address")
   if(dev==0) {
    printf("ICEPAPMEAS ERROR: \"%s\": missing ADDR field\n", mne)
    return ".error."
   }
   if(index(dev,":") == 0) { dev=sprintf("%s:5000",dev) }
   ICEPAP_MEAS[mne]["dev"]=dev

   # get the command to read the DSP internals
   cmd=counter_par(num,"cmd")
   if(cmd==0) {
    printf("ICEPAPMEAS ERROR: \"%s\": missing \"cmd\" parameter\n", mne)
    return ".error."
   }
   ICEPAP_MEAS[mne]["cmd"]=cmd
 }
}'


#%IU%
#%MDESC%
#
def icepapmeas_cmd(num,key,p1,p2) '{
 global ICEPAP_MEAS[]
 local mne
 local addr
 local dev
 local cmd


 if (num != "..") {
  mne =cnt_mne(num)
  addr=ICEPAP_MEAS[mne]["addr"]
  dev =ICEPAP_MEAS[mne]["dev"]
  cmd =ICEPAP_MEAS[mne]["cmd"]
 }

 if (key == "start_one") {
  icepap_cleanup(dev)
 }

 if (key == "counts") {
  local ans
  local t

  ans= _icepap_query(dev,addr,"?"cmd)

  if(index(ans,"ERROR")) {
   printf("ICEPAPMEAS ERROR: \"%s\": unable to get value\n",mne)
   return(0)
  }

  if(sscanf(ans,"%lf",t) != 1) {
   printf("ICEPAPMEAS ERROR: \"%s\": addr: %s: unable to get value\n",mne,addr)
   icepap_chkerr(dev,ans)
   icepap_fiforst(dev)
   return(0)
  }
  return(t)
 }

 if (key == "halt_one") {
  icepap_cleanup(dev)
 }
}'







#
# ----------------------- direct accces macros -------------------------
#



#%UU% hostname cmd [args]
#%MDESC%
# Execute the specified command with its args on all 
# ICEPAP modules (MASTER, SLAVES and DRIVERS) known by the hostname.
# Note that quotes must be used when cmd includes a hash for the
# acknowledge.
#
def icepap_all '{
 local dev
 local cmd
 local i n


 # Minimum check

 if($# < 2) {
  p "Usage: $0 icepap cmd [args]"
  p "   ex: $0 iceid131 ?ver info"
  p "   ex: $0 iceid131 \"\#power\" off"
  exit
 }

 # Get args
 dev = "$1"
 if(!index(dev,":")) { dev = dev":5000" }
 ICEPAP["dev"]=dev

 # Get args
 i = 1
 n = split("$*", args)
 for(cmd=args[i++];i<n;i++) {
    cmd = cmd " " args[i]
 }
 
 _icepap_all(dev, cmd)
}' 


#%IU%(hostname, cmd, prefix_string)
#%MDESC%
#
def _icepap_all(dev, cmd, prefix_string, only_slaves, only_drivers) '{
 local ans
 local addrs
 local i n a
 local prefix

 if(!index(dev,":")) { dev = dev":5000" }
 ICEPAP["dev"]=dev

 # Optional line prefix
 prefix = (prefix_string)?prefix_string:""

 # Not needed
 cmd = icepap__toupper(cmd)
 
 # Just in case
 icepap_fiforst(dev)

 # Get the list of modules
 ans = _icepap_get_addrs(dev)
 n   = split(ans, addrs)

 # Execute the command on each module
 for(i=0;i<n;i++) {
  a = addrs[i]
  if(only_slaves) {
    if(a%10) {
      continue
    }
  }

  if(only_drivers) {
    if((a%10) == 0) {
      continue
    }
  }

  if(substr(cmd,1,1)=="?") {
   p prefix,a,_icepap_wrrd(dev,a,cmd);
  } else if(substr(cmd,1,1)=="#") {
   p prefix,a,_icepap_wrrd(dev,"#" a,substr(cmd,2));
  } else {
   _icepap_wr(dev,a,cmd);
  }
 }

}'


#%UU% [option] cmd
#%MDESC%
# Execute the specified command with its args on all 
# ESRF ICEPAP systems which follow the naming convention 
# (hostname starting with "ice...")
# The option could be "slaves" to execute the command only
# on MASTERS and SLAVES modules.
#
def icepap_all_esrf '{
  local cmd args[]
  local i n hosts[]
  local dev
  local slaves_only drivers_only debug_only


  # minimum check
  if($# < 1) {
    p "Usage : $0 [option] cmd [args]"
    p "Option: MASTERS | SLAVES | DRIVERS | DEBUG"
    p "    ex: $0 ?ver info"
    p "    ex: $0 MASTERS ?ver saved"
    exit
  }

  # Grant access to avoid abusive use
  onwiz 0
  if(!spec_par("specwiz")) {
    exit
  }

  # by default execute the command on MASTERS, SLAVES and DRIVERS
  slaves_only  = 0
  drivers_only = 0
  debug_only   = 0

  # 
  i = 0
  n = split("$*", args)
  for(;;) {
    if(icepap__toupper(args[i]) == "DEBUG") {
      i++
      debug_only = 1
      continue
    }
    if(icepap__toupper(args[i]) == "SLAVES") {
      i++
      slaves_only = 1
      continue
    }
    if(icepap__toupper(args[i]) == "DRIVERS") {
      i++
      drivers_only = 1
      continue
    }
    break
  }
   
  for(cmd=args[i++];i<n;i++) {
    cmd = cmd " " args[i]
  }


  if(debug_only) {
    n = 0
    hosts[n++]="iceeu4"
    hosts[n++]="icebcu1"
  } else {
    n = _icepap_get_all_hostnames(hosts)
    if(n < 0) {
      exit
    }
    if(n == 0) {
      print "nothing to do, giving up"
      exit
    }
  }

  for(i=0;i<n;i++) {
    host = hosts[i]
    dev  = host ":5000"

    # avoid timeout messages
    if(!sock_par(dev, "connect", "silent")) {
      continue
    }

    _icepap_all(host, cmd, host, slaves_only, drivers_only)

    sock_par(dev, "close")
  }
}'

#%IU% [hostname [hostname [...]]]
#%MDESC%
# Print out the rack ID of all ESRF ICEPAP racks
#
def _icepap_rid_esrf '{
  local i j n m hosts[]
  local dev host
  local ans addrs[] ids[]
  local slaves_only


  # Grant access to avoid abusive use
  onwiz 0
  if(!spec_par("specwiz")) {
    exit
  }

  # 
  slaves_only = 1

  #
  if($#) {
    n = split("$*", hosts)
  } else {
    n = _icepap_get_all_hostnames(hosts)
  }

  if(n < 0) {
    exit
  }
  if(n == 0) {
    print "nothing to do, giving up"
    exit
  }


  for(i=0;i<n;i++) {
    host = hosts[i]
    dev  = host ":5000"

    # avoid timeout messages
    if(!sock_par(dev, "connect", "silent")) {
      continue
    }

    ans = _icepap_get_addrs(dev, slaves_only)
    m   = split(ans, addrs)
    for(cmd="?RID", j=0;j<m;j++) {
      cmd = sprintf("%s %d", cmd, addrs[j]/10)
    }
    ans = _icepap_wrrd(dev, "", cmd)
    m   = split(ans, ids)
    for(j=1;j<m;j++) {
      printf("%15s %2d %s\n", host, addrs[j-1]/10, ids[j])
    }

    sock_par(dev, "close")
  }
}'


#%IU%(host[])
#%MDESC%
# Fills the given associative array with all ICEPAP hostnames
# found in DNS and which start with "ice..."
# Returns the number of system or -1 if error occured.
#
def _icepap_get_all_hostnames(hosts) '{
  local i cmd aux lines[]
  local j host

  cmd = "host -l \"esrf.fr\" | grep ice | cut -d\' \' -f1"
  cmd = cmd " | cut -d\'.\' -f1"
  cmd = cmd " | grep -v nice"
  if(unix(cmd, aux) != 0) {
   _icepap_err
   print "error accessing DNS to list IcePAP hostnames"
   return(-1)
  }

  split(aux, lines, "\n")
  for(i in lines) {
    host = lines[i]
    if(substr(host, 0, 3) != "ice") {
      continue
    }
    hosts[j++] = host
  }

  return(j)
}'


#%IU%(hostname, slaves_only)
#%MDESC%
# Returns the addresses of all ICEPAP modules alive on the
# specficed hostname. The addresses are returned in a single string,
# blank separated.
#
def _icepap_get_addrs(dev, slaves_only) '{
 local cmd_ans
 local addrs
 local rflg
 local r
 local dflg
 local d
 local a


 addrs   = ""
 cmd_ans = _icepap_query(dev,"","?SYSSTAT") 
 if(index(cmd_ans,"ERROR"))
 {
  printf("ICEPAP: sorry, too old firmware to guess racks present\n")
  return(addrs) 
 }

 sscanf(cmd_ans,"%x",rflg)
 for(r=0;r<16;r++)
 {
  if(!(rflg & (1<<r))) { continue }

  cmd_ans = _icepap_query(dev,"",sprintf("?SYSSTAT %d",r)) 
  if(index(cmd_ans,"ERROR")) return(addrs)

  cmd_ans=substr(cmd_ans,index(cmd_ans," ")+1)
  sscanf(cmd_ans,"%x",dflg)

  for(d=0;d<=8;d++)
  {
   if((d!=0) && !(dflg & (1<<(d-1)))) { continue }

   a=(d)+(r*10)
   addrs = sprintf("%s%d ",addrs,a)

   if(slaves_only) {
    break  
   }
  }

 }

  return(addrs) 
}'




#%IU%(bliss_package_name)
#%MDESC%
#
def _is_package_installed(pkg) '{
 local ll
 local cmd

 cmd = "rpm --dbpath ~blissadm/admin/RPM -qa"
 cmd = sprintf("%s | grep \"%s\"",cmd, pkg)
 unix(cmd,ll)
 if(ll=="") {
   p "ERROR: missing package \"",pkg,"\""
   p "HINT : use \"blissinstaller\""
   exit
 }
}'


#%IU%(icepap_hostname)
#%MDESC%
#
def _is_same_subnetwork(dev) '{
 local ll
 local cmd
 local dev_ip[]
 local host_ip[]
 
 cmd = sprintf("nslookup `hostname`")
 cmd = sprintf("%s |grep -i \"address\"|grep -v \"#\"|cut -d\':\' -f2",cmd)
 unix(cmd, ll)
 if(ll=="") {
   p "ERROR: unable to get hostname IP, giving up"
   exit
 }
 split(ll, host_ip, ".")

 cmd = sprintf("nslookup %s", dev)
 cmd = sprintf("%s |grep -i \"address\"|grep -v \"#\"|cut -d\':\' -f2",cmd)
 unix(cmd, ll)
 if(ll=="") {
   p "ERROR: unable to get \""dev"\" IP, giving up"
   exit
 }

 split(ll, dev_ip, ".")
 if(dev_ip[2] != host_ip[2]) {
   p "ERROR: not on same sub-network than \""dev"\", giving up"
   p "HINT : use spec on another machine"
   exit
 }
}'

#%IU%(firmwares_list)
#%MDESC%
#
def _is_valid_version(dev, ll) '{
 local fw_list[]
 local fw_n
 local fw_cur
 local ans

 fw_n = split(ll,fw_list)
 ans=_icepap_query(dev, "", "?VER")
 if(index(ans,"ERROR"))
 {
  p "ERROR: unable to get icepap system version, giving up"
  p "HINT : switch on \""dev"\""
  exit
 }
 fw_cur=icepap_trim(ans)
 for(n in fw_list) {
  if(fw_list[n] == fw_cur)
    return
 }

 p "ERROR: wrong current firmware version \""fw_cur"\" on \""dev"\""
 p "HINT : must be one of these \""ll"\""
 exit
}'


#%IU%(icepap_hostname)
#%MDESC%
#
def _is_alive(dev) '{
  local silent
  local ans
  local cmd
  local n

  cmd = sprintf("ping -c 1 %s > /dev/null",dev)
  for(n=5;n;n--) {
   if(!unix(cmd)) { 
    # Linux is up but give time to the embedded program to start
    sleep(1)

    # Restore SPEC socket connection
    if(!index(dev,":")) { dev = dev":5000" }
    sock_par(dev,"close")
    sock_par(dev,"connect")

    # Check that the embedded program started well
    silent = 1
    ans=_icepap_query(dev,0,"?ID", silent)
    if(ans == "") { 
     p "ERROR: the embedded program is not running on \""dev"\""
     p "HINT : reboot it by cycling its power supply (off/on)"
     return ".error." 
    }

    # Normal end
    return ""
   }
   sleep(1)
  }
  
  p "ERROR: timeout waiting for \""dev"\" to be alive"
  return ".error." 
}'

#%IU%(dev, addr_list)
#%MDESC%
# Bypass external disable signal on the list of drivers given.
# (this was done with DISDIS command on old firmwares <2.0)
# Works only on firmware >=2.0 
#
def _emulate_alldis(dev, addr_list) '{
 local ll
 local i

 split(addr_list, ll)
 for(i in ll) {
  _emulate_onedis(dev, ll[i])
 }
}'


#%IU%(dev, addr)
#%MDESC%
# Bypass external disable signal on the driver given.
# (this was done with DISDIS command on old firmwares <2.0)
# Works only on firmware >=2.0 
#
def _emulate_onedis(dev, addr) '{
 local ans
 local cmd
 local signature

 ans = _icepap_query(dev, addr, "?MODE")
 if(!index(ans,"OPER"))
 {
  p "ERROR: driver \""addr"\" not in OPER mode, giving up"
  exit
 }

 ans = _icepap_query(dev, addr, "?CONFIG")
 signature = ans

 cmd = sprintf("%sCONFIG",spec_par("specwiz")?"BD_":"")
 ans = _icepap_query(dev, "#"addr, cmd)
 if(index(ans,"ERROR"))
 {
  p "ERROR: unable to enter CONFIG on driver \""addr"\", giving up"
  exit
 }

 cmd = sprintf("%sCFG EXTDISABLE NONE",spec_par("specwiz")?"BD_":"")
 ans = _icepap_query(dev, "#"addr, cmd)
 if(index(ans,"ERROR"))
 {
  p "ERROR: unable to change EXTDISABLE on driver \""addr"\", giving up"
  exit
 }

 cmd = sprintf("%sCONFIG \"%s\"",spec_par("specwiz")?"BD_":"", signature)
 ans = _icepap_query(dev, "#"addr, cmd)
 if(index(ans,"ERROR"))
 {
  p "ERROR: unable to change CONFIG on driver \""addr"\", giving up"
  exit
 }

}'


#%UU% hostname
#%MDESC%
# Upgrade the specified icepap system from firmware 1.22 to 2.0
# If called from SPEC wizard mode, the security of the mandatory
# same subnetwork is bypassed.
#
def upgrade_firmware_1_22 '{
 local  dev
 local  fname
 local  addr
 local  opt
 local  dis_list
 local  msg
 local  i


 # Minimum check
 if($# < 1) {
  p "Usage: $0 hostname"
  p "   ex: $0 iceid131"
  exit
 }
 dev = "$1"
 
 

 # Dangerous macro
 p "\nWARNING: this macro will upgrade all the firmwares of \"$1\""
 if(!yesno("Do you want to continue",0)) {
  p "Giving up"
  exit
 }
 p ""


 # Check that packages have been upgarded
 _is_package_installed("icepapcms-src-1.28")
 _is_package_installed("icepap_firmware-src-2.0")


 # Check that the system has firmware version 1.22 or 1.225
 _is_valid_version(dev, "1.22 1.225")


 # Check that we are on the same subnetwork
 if(!spec_par("specwiz")) {
  _is_same_subnetwork(dev)
 }


 # Do an inventory of axis with external signal disabled
 p "Collectng list of axis with external disable signal bypassed"
 dis_list = _icepap_alldis(dev, 1)

 # Keep inventory into a file

 # Upgrade firmwares
 fname = "/users/blissadm/local/userconf/icepap/icepfw_2.0"
 addr  = "ALL"
 opt   = "FORCE"
 p "Upgrading FPGA/DSP firmwares"
 _icepap_prog(dev,fname,addr,opt)


 # Upgrade MCPUx firmwares
 addr  = "MCPU0"
 p "Upgrading embedded Linux firmwares"
 _icepap_prog(dev,fname,addr,opt)
 addr  = "MCPU1"
 _icepap_prog(dev,fname,addr,opt)
 addr  = "MCPU2"
 _icepap_prog(dev,fname,addr,opt)


 # Save new firmware into the icepap system flash
 addr  = "NONE"
 opt   = "SAVE"
 p "Saving new firmware package into embedded flash"
 _icepap_prog(dev,fname,addr,opt)


 # Reboot the icepap system to run the new MCPUx and set RDISPOL
 p "Reboot the system and wait for its wake up"
 _icepap_query(dev,"#","REBOOT")
 sleep(20)
 if(_is_alive(dev) != "") {
   p "HINT : after the reboot run the following macro:"
   p sprintf("_emulate_alldis(\"%s\",\"%s\")",dev, dis_list)
   exit
 }


 # Restore external disable signal
 p "Restoring external disable signal bypasses"
 _emulate_alldis(dev, dis_list)


 # Normal end
 p "Bravo\! System should be upgraded"
 if(i=index(dev,":")) { dev = substr(dev, 0, i-1) }
 msg = sprintf("%s", date())
 msg = sprintf("%s\nsystem      : %s", msg, dev)
 msg = sprintf("%s\nupgraded to : 2.0", msg)
 msg = sprintf("%s\nDISDIS kept : %s", msg, dis_list)
 _icepap_email(sprintf("%s: UPGRADE", dev), msg)
}'





#%UU% hostname
#%MDESC%
# Will try to guess if the DISDIS parameter is really needed on
# each DRIVER of the specified ICEPAP systerm. If not needed, the
# macro will remove it.
# WARNING: this macro is for temporary usage only.
#
def icepap_alldis '{
 local dev
 local silent


 # Minimum check
 if($# < 1) {
  p "Usage: $0 icepap"
  p "   ex: $0 iceid131"
  exit
 }

 # Get args
 dev = "$1"
 ICEPAP["dev"]=dev

 silent = 0
 _icepap_alldis(dev, silent)
}' 


def _icepap_alldis(dev, silent) '{
 global ICEPAP[]
 local  cmd_ans
 local  rflg
 local  r
 local  dflg
 local  d
 local  addr
 local  was_on
 local  addr_list
 local  del_list
 local  msg
 local  i



 # Just in case
 if(!index(dev,":")) { dev = dev":5000" }
 icepap_fiforst(dev)

 # Check if firmware is a valid one
 cmd_ans = _icepap_wrrd(dev,"","?SYSSTAT") 
 if(index(cmd_ans,"ERROR"))
 {
  printf("ICEPAP: sorry, too old firmware to guess racks present\n")
  exit
 }

 addr_list = ""
 del_list  = ""
 cmd_ans = _icepap_query(dev,"","?SYSSTAT") 
 sscanf(cmd_ans,"%x",rflg)
 for(r=0;r<16;r++)
 {
  if(!(rflg & (1<<r))) { continue }

  cmd_ans = _icepap_query(dev,"",sprintf("?SYSSTAT %d",r)) 
  if(index(cmd_ans,"ERROR")) exit;

  cmd_ans=substr(cmd_ans,index(cmd_ans," ")+1)
  sscanf(cmd_ans,"%x",dflg)

  # Skip COMM or MASTER modules
  for(d=1;d<=8;d++)
  {
   if((d!=0) && !(dflg & (1<<(d-1)))) { continue }

   addr=(d)+(r*10)
   cmd_ans = _icepap_query(dev,addr,"?DISDIS") 
   if(index(cmd_ans,"ERROR")) continue;
   if(int(cmd_ans) != 1) { continue }
  
   if(!silent) { printf("Found driver with DISDIS set: %02d\n",addr) }
  
   # Keep a record of the current state in any case
   cmd_ans=_icepap_query(dev,addr,"?POWER")
   was_on =(index(cmd_ans,"ON")?1:0)
   if(!silent) { printf("\tPOWER is  %s\n",(was_on?"ON":"OFF")) }

   # Try to disable DISDIS
   if(was_on)
   {
    cmd_ans=_icepap_query(dev,"#" addr,"POWER OFF")
    if(index(cmd_ans,"ERROR")) 
    {
     printf("ERROR: switching off power\n")
     printf("Giving up\n")
     continue;
    }
   }

   cmd_ans=_icepap_query(dev,"#" addr,"DISDIS 0")
   if(index(cmd_ans,"ERROR")) 
   {
    printf("ERROR: removing DISDIS parameter\n")
    printf("Giving up\n")
    continue;
   }

   # Try to guess if DISDIS is really needed
   cmd_ans=_icepap_query(dev,addr,"?ISG ?PWRINFO")
   if(index(cmd_ans,"axis remote disable signal on")) 
   {
    if(!silent) { printf("\tDISDIS needed\n") }
    addr_list = sprintf("%s%s ", addr_list, addr)
    cmd_ans=_icepap_query(dev,"#" addr,"DISDIS 1")
    if(index(cmd_ans,"ERROR")) 
    {
     printf("ERROR: setting DISDIS parameter\n")
    }
   }
   else
   {
    del_list = sprintf("%s%s ", del_list, addr)
    if(!silent) { printf("\tDISDIS not needed, removed !!!!!!!!!!!!!!\n") }
   }

   # Put back power if needed
   if(was_on)
   {
    if(!silent) { printf("\tPOWER set %s\n",(was_on?"ON":"OFF")) }
    cmd_ans=_icepap_query(dev,"#" addr,"POWER ON")
    if(index(cmd_ans,"ERROR")) 
    {
     printf("ERROR: switching on  power\n")
     printf("Giving up\n")
     continue;
    }
   }
  }

 }

 printf("\tDISDIS kept           : \"%s\"\n",addr_list)
 printf("\tDISDIS deleted        : \"%s\"\n",del_list)

 # Normal end, return the list of drivers than need DISDIS
 if(i=index(dev,":")) { dev = substr(dev, 0, i-1) }
 msg = sprintf("%s", date())
 msg = sprintf("%s\n%s", msg, dev)
 msg = sprintf("%s\nDISDIS kept   : %s", msg, addr_list)
 msg = sprintf("%s\nDISDIS deleted: %s", msg, del_list)
 _icepap_email(sprintf(" %s: DISDIS", dev), msg)
 return addr_list

}'




#%UU% [hostname[:port]]
#%MDESC%
# Console implementation to communicate to icepap MASTER
#
def icepap '{
 global ICEPAP[]
 global ICEPAP_NCOMM
 global ICEPAP_HIST[]
 global ICEPAP_NHIST
 global ICEPAP_CHIST
 local  dev
 local  line c fc
 local  args[]
 local  i inst
 local  begline endline
 

 if($# < 1) {
  dev=getval("Icepap MASTER network name",ICEPAP["dev"])
 } else {
  dev="$1"
 }

 if(!index(dev,":")) { dev = dev":5000" }
 ICEPAP["dev"]=dev


 cdef("cleanup_once",sprintf("_icepap_close(\"%s\")\n",dev),"ICEPAP")

 if(sock_put(dev, "?_help\n") == -1) { exit }
 sleep(0.1)
 sock_par(dev,"timeout",2)
 p "\n" sock_get(dev)
 
 while(1) {
  line = ""
  inst = 0
  printf("\n%d.ICEPAP console> ",ICEPAP_NCOMM);
  while(1) {
    c = input(-1)

    if (c == "\n") {
      # handle carriage return
      printf("\n")
      tty_cntl("cd")
      break
    } 

    # handle backspace
    if ((c == "\b" || c == "\177") && (line != "")) {
      if((length(line)+inst)<=0) {
       continue
      }
      begline = substr(line, 0, length(line)+inst-1)
      endline = substr(line, length(line)+inst+1) 
      line = begline endline
      printf("\b%s ",endline)      
      for(i=0;i>=inst;i--) {
       printf("\b")
      } 

      continue
    }

    # handle arrow keys
    if (asc(c) == 27) {
 
      # arrow keys send 3 control chars
      sleep(0.05)
      c = input(-1)
      sleep(0.01)
      c = input(-1)

      # left  arrow
      if(asc(c) == 0x44) {
       if((length(line)+inst)<=0) {
        continue
       }
       printf("\b")
       inst--
      }

      # right arrow
      if(asc(c) == 0x43) {
       if(inst>=0) {
        continue
       }
       i = length(line)+inst
       for(;i;i--) {
        printf("\b")
       } 
       inst++
       begline = substr(line, 0, length(line)+inst)
       printf(begline)
      }

      # up arrow
      if(asc(c) == 0x41) {
       if(ICEPAP_CHIST) { 
        line = ICEPAP_HIST[--ICEPAP_CHIST] 
        printf("\r")
        tty_cntl("cd")
        printf("%d.ICEPAP console> %s",ICEPAP_NCOMM,line);
       }
      }

      # down arrow
      if(asc(c) == 0x42) {
       if(ICEPAP_CHIST<ICEPAP_NHIST) { 
        line = ICEPAP_HIST[++ICEPAP_CHIST] 
        printf("\r")
        tty_cntl("cd")
        printf("%d.ICEPAP console> %s",ICEPAP_NCOMM,line);
       }
      }

      continue
    }

    # append new printable character 
    if (c >= " " && c <= "z") {
      printf(c)
      if(!inst) {
       line = line c
       continue
      }
      begline = substr(line, 0, length(line)+inst)
      endline = substr(line, length(line)+inst+1) 
      line = begline c endline
      printf("%s",endline)      
      for(i=0;i>inst;i--) {
       printf("\b")
      } 
    }

    sleep(0.01)
  }

  # keep an history of all entered commands
  if(line) {
   ICEPAP_HIST[ICEPAP_NHIST]=line
   ICEPAP_NHIST++
   ICEPAP_CHIST=ICEPAP_NHIST
  }

  split(line,args)
  if(!args[0]) {
    continue
  }
  if(whatis(args[0]) & 0x00000002) {
    eval(line)
    continue
  }

  uline = icepap__toupper(line)
  if((uline == "QUIT") || (uline == "CLOSE")) {
   sock_par(dev,"close")
   exit
  }

  fc = substr(line,1,1) 
  if((fc!="#") && (fc!=":")) {
   line = "#" line
  }

  # acknowledge of broadcast commands is forbidden
  if(fc==":") {
    _icepap_wr(dev,"",line)
  } else {
    ans = _icepap_query(dev,"",line,1)
    p icepap_trim(ans)
  }
  ICEPAP_NCOMM++
 }
}'



#%IU%(string, n)
#%MDESC%
# Parse the string to get the ICEPAP device to talk to.
# Try to get as nth argument (starting from 1) in the string, 
# if not prompt the user.
#
def _icepap_getdev(str, i) '{
 local args[]
 local n
 local dev
 local sl

 n=split(str,args)

 if(n < i) {
  dev=getval("Icepap MASTER network name or SL",ICEPAP["dev"])
 } else {
  dev=args[i-1]
 }

 sl = (dev + 0 == dev)?1:0
 if(!sl && !index(dev,":")) { dev = dev":5000" }
 ICEPAP["dev"]=dev

 return(dev)
}'




#%IU%(string, n)
#%MDESC%
# Parse the string to get the ICEPAP tocho file.
# Try to get as nth argument (starting from 1) in the string, 
# if not prompt the user.
#
def _icepap_getfname(str, i) '{
 local args[]
 local n
 local fname
 local fdir
 local uxcom
 local aux
 local m
 local flist[]
 local f


 n=split(str,args)

 if(n < i) {
  fdir=sprintf("%s/%s",BLISSADM,"local/userconf/icepap");
  if(!file_info(fdir)) {
   print "ERROR: missing icepap_firmware, hint: use \"blissinstaller\""
   exit
  }
  
  uxcom = "cd " fdir "; ls -rt"
  unix(uxcom, aux)
  m = split(aux, flist, "\n")
  for(m--;m;m--)
  {
   f=flist[m]
   if(f=="") continue;
   if(file_info(sprintf("%s/%s",fdir,f),"isdir")) continue;
   break;
  }

  fname=sprintf("%s/%s",fdir,flist[m])
  fname=getval("Binary program file",fname)
 } else {
  fname=args[i-1]
 }

 if(file_info(fname,"-r") == 0) {
  print "ERROR: unable to read file \""fname"\""
  exit
 } 

 return(fname)
}'



#%IU%(string, n)
#%MDESC%
# Parse the string to get the addr for programming.
# Try to get as nth argument (starting from 1) in the string, 
# if not prompt the user.
#
def _icepap_getaddr(str, i) '{
 local args[]
 local n
 local addr

 n=split(str,args)

 if(n < i) {
  addr=getval("What to program (\"ALL\"|\"DRIVERS\"|\"CONTROLLERS\"|\"NONE\"|addr) ",\
     "NONE")
 } else {
  addr=args[i-1]
 }
 addr = icepap__toupper(addr)
 if(addr=="NONE") { addr="" }

 return(addr)
}'


#%IU%(string, n)
#%MDESC%
# Parse the string to get the options for programming.
# Try to get as nth argument (starting from 1) in the string, 
# if not prompt the user. 
# Any remaining argument is also return as option.
#
def _icepap_getopts(str, i) '{
 local args[]
 local n
 local opt

 n=split(str,args)

 if(n < i) {
  opt=getval("Options (\"SAVE\"|\"SL\"|\"FORCE\"|\"NONE\") ","NONE")
 } else {
  opt=args[i-1]
  for(;i<n;i++) { opt=opt " " args[i] }
 }
 opt = icepap__toupper(opt)
 if(opt =="NONE") { opt ="" }

 return(opt)
}'

#%IU%(dev, filename, address, options)
#%MDESC%
# Check that the giving programming options are compatible with
# system to upgrade.
# Returns non null if error.
#
def _icepap_checkprog(dev, fname, addr, opts) '{
  local tt[]
  local ver
  local ver_sl
  local ret

  #
  # only old "tochos" can be used over serial line
  #

  # return if no serial line prog will take place
  if(!index(icepap__toupper(opts), "SL")) {
   return 0
  }

  # get firmware version
  if(split(fname, tt, "icepfw_") < 2) {
   _icepap_err
   print "unable to parse firmware file name:"
   print fname
   return -1
  }
  ver = icepap_trim(tt[1])
 
  ver_sl = "2.115"
  if(_icepap_fw_compare(ver, ver_sl) != 0) {
   _icepap_err
   printf("only firmware %s can be used over serial line\n", ver_sl)
   return -1
  }

  # get system version
  ver = _icepap_fw_controller(dev)
  ret = _icepap_fw_compare(ver, ver_sl)

  if((ret < 0) || (ret > 1)) {
   _icepap_err
   printf("upgrade MASTER to at least firmware %s\n", ver_sl)
   return -1
  }
   
  
  # normal end
  return 0
}'


#%IU%(string, n)
#%MDESC%
# Parse the string to get the rack addresses for programming.
# Try to get as nth argument (starting from 1) in the string, 
# if not prompt the user. 
# Any remaining argument is also return as option.
#
def _icepap_getracks(str, i) '{
 local args[]
 local n
 local racks

 n=split(str,args)

 if(n < i) {
  racks=getval("List of racks (ex: 0 3)",0)
 } else {
  racks=args[i-1]
  for(;i<n;i++) { racks=racks " " args[i] }
 }
 racks = icepap__toupper(racks)

 return(racks)
}'




#%UU% [hostname[:port] [file] [rack ... ]]
#%MDESC%
# Program a the ICEPAP specified list of racks using the rack back plane
# serial line.
#
def icepap_rprog '{
 local dev
 local fname
 local racks
 local sl
 local cmd_ans


 # Get args or prompt them to the user
 dev   = _icepap_getdev  ("$*", 1)
 fname = _icepap_getfname("$*", 2)
 racks = _icepap_getracks("$*", 3)

 sl    = (dev + 0 == dev)?1:0


 # Get a connection to ICEPAP
 if(!sl) { _icepap_check(dev) }


 # Send this with acknowledge to ensure that all DRIVERs mode have changed
 cmd = "#MODE PROG"
 printf("\tCommand sent  : \"%s\"\n", cmd)
 if(sl) 
 { 
  ser_put(dev,sprintf("%s\n",cmd)) 
 }  
 else 
 { 
  cmd_ans = _icepap_wrrd(dev,"",cmd) 
  if(!index(cmd_ans,"MODE OK"))
  {
   _icepap_err
   print "unable to switch ICEPAP to programmation mode"
   exit
  }
 }
 
 
 cmd = sprintf("*RPROG %s",racks)
 printf("\tCommand sent  : \"%s\"\n", cmd)
 if(sl) { ser_put(dev,sprintf("%s\n",cmd)) } else { _icepap_wr(dev,"",cmd) }


 # Send the binary data
# _icepap_wrbin(dev,fname)

}'


#%UU% [hostname[:port] [file] [addr] [options]]
#%MDESC%
# Program a the ICEPAP module with the specified binary file
#
def icepap_prog '{
 local dev
 local fname
 local addr
 local opt
 

 # Get args or prompt them to the user
 dev   = _icepap_getdev  ("$*", 1)
 fname = _icepap_getfname("$*", 2)
 addr  = _icepap_getaddr ("$*", 3)
 opt   = _icepap_getopts ("$*", 4)

 if(_icepap_checkprog(dev, fname, addr, opt)) {
  exit
 }

 _icepap_prog(dev,fname,addr,opt)
}'


#%IU%(dev,fname,addr,opt)
#%MDESC%
# Program a the ICEPAP module with the specified arguments.
# If no firmware file name is given, the programmatino will used
# the firmware saved in the controller flash.
#
def _icepap_prog(dev,fname,addr,opt) '{
 global ICEPAP[]
 local sl
 local cmd
 local cmd_ans
 local dest_mcpu
 local dest_none
 local use_saved
 local dodo

 # Get a connection to ICEPAP
 sl    = (dev + 0 == dev)?1:0
 if(!sl && !index(dev,":")) { dev = dev":5000" }
 if(!sl) { _icepap_check(dev) }

 dest_mcpu=index(addr,"MCPU")
 dest_none=(addr=="")?1:0

 # Minimum check on parameters validity
 if(dest_none && opt == "")
 {
  _icepap_err
  print "nothing to do, giving up"
  exit
 }

 # If no firmware file, then use the saved one
 use_saved = (fname=="")

 # Skip mode handling if no DSP or FPGA is concerned
 if(!dest_none && !dest_mcpu)
 {
  # Send this with acknowledge to ensure that all DRIVERs mode have changed
  cmd = "#MODE PROG"
  printf("\tCommand sent  : \"%s\"\n", cmd)
  if(sl) 
  { 
   ser_put(dev,sprintf("%s\n",cmd)) 
  }  
  else 
  { 
   cmd_ans = _icepap_wrrd(dev,"",cmd) 
   if(!index(cmd_ans,"MODE OK"))
   {
    _icepap_err
    print "unable to switch ICEPAP to programmation mode"
    exit
   }
  }
 }
 
 
 cmd = sprintf("%sPROG %s %s",(use_saved?"":"*"),addr, opt)
 printf("\tCommand sent  : \"%s\"\n", cmd)
 if(sl) { ser_put(dev,sprintf("%s\n",cmd)) } else { 
  _icepap_wr(dev,"",cmd) 
 }


 # Send the binary data
 if(!use_saved) { _icepap_wrbin(dev,fname) }
 

 #Skip mode handling if no DSP or FPGA is concerned
 #if(!dest_none && !dest_mcpu)
 if(!dest_mcpu)
 {
  ans="?????"
  printf("\tState         : %-20s\r",ans)

  # NOTE MP 08Apr11:  should be replaced by an acknowled on *PROG cmd
  sleep(1)

  cmd="?_PROG"
  if(index((_icepap_wrrd(dev,"",cmd)),"ERROR")) { cmd="?PROG" }

  for(;index((ans= _icepap_query(dev,"",cmd)),"ACTIVE");sleep(.5))
   printf("\tState         : %-20s\r",ans);

  printf("\tState         : %-20s\n",ans)
 
  sleep(2)
 }
 else
 {
  ans=" DONE"
  printf("\tState         :%-20s\n",ans)
 }


 if(!dest_none && !dest_mcpu)
 {
  # Give time to the drivers to come back to life
  dodo = 5
  printf("\tWait for      : %dsecs\n", dodo)
  sleep(dodo)

  cmd = "#MODE OPER"
  printf("\tCommand sent  : \"%s\"\n", cmd)
  if(sl)
  {
   ser_put(dev,sprintf("%s\n",cmd))
  }
  else
  {
   cmd_ans = _icepap_wrrd(dev,"",cmd)
   if(!index(cmd_ans,"MODE OK"))
   {
    _icepap_err
    print "unable to switch ICEPAP to operation mode"
    exit
   }
  }

 }

 # Check if a reboot is needed
 cmd="?VER"
 if(index((_icepap_wrrd(dev,"",cmd)),"ERROR Reboot is needed")) 
 { 
  cmd = "REBOOT"
  printf("\tCommand sent  : \"%s\"\n", cmd)
  printf("NOTE: the connection to \"%s\" will be unusable during 30s\n",dev)
  _icepap_wr(dev,"",cmd) 
 }
}'



#%IU%(dev,fname)
#%MDESC%
# Send the contain of the file to the ICEPAP using the ISG
# binary transfer protocol.
#
def _icepap_wrbin(dev,fname) '{
 local sl
 local bin_l
 local bin_chksum


 sl    = (dev + 0 == dev)?1:0

 bin_l = (file_info(fname,"size")/2) & 0xffffffff

 local ushort array icepapfw[bin_l]
 fmt_read(fname,"raw",icepapfw)

 # 2 words startup mark (ex: 0xa5aa555a)
 local ulong array datal[1]
 datal[0]=0xa5aa555a
 if(sl) { ser_put(dev, datal) } else { sock_put(dev, datal) }


 # 2 words for the binary data length
 datal[0]=bin_l
 if(sl) { ser_put(dev, datal) } else { sock_put(dev, datal) }


 # 2 words for the checksum 
 bin_chksum = (array_op("sum",icepapfw)) & 0xffffffff
 printf("\tCheck sum     : 0x%08X\n", bin_chksum)
 datal[0]=bin_chksum
 if(sl) { ser_put(dev, datal) } else { sock_put(dev, datal) }

 # chaud devant !!!
 printf("\tTransferring  : %d words\n", bin_l)
 if(sl) { ser_put(dev, icepapfw) } else { sock_put(dev, icepapfw) }
}'




#%IU% [hostname:port]
#%MDESC%
# Reset the MASTER DSP
#
def icepap_mdspreset '{
 global ICEPAP[]
 local dev

 if($# < 1) {
  dev=getval("Icepap MASTER network name",ICEPAP["dev"])
 } else {
  dev="$1"
 }

 if(!index(dev,":")) { dev = dev":5000" }
 ICEPAP["dev"]=dev

 sock_put(dev,"_dsprst\n")
 print "MASTER DSP reseted"
}'




#%IU% [hostname:port [driver]]
#%MDESC%
# Program one or all DRIVER DSP numbered from 1 to 8
# using the backplane serial line and the DDSP programm taken
# from MDSP flash (no binary transferred)
#
def icepap_ddsppgm_sl '{
 local dev
 local board

 if($# < 1) {
  dev=getval("Icepap MASTER network name","isgtmp5:5000")
 } else {
  dev="$1"
 }

 _icepap_check(dev)

 # Ya un couillon au clavier ?
 board=0
 if($# < 2) { 
  board=getval("Icepap DRIVER to program from 1 to 8 (0 for all)",board)
 } else {
  board=$2 
 }

 if((board<0) || (board>8)) 
 {
  print "ICEPAP: invalid channel/board "board" (valid: 1 to 8 or 0 for all)"
  return
 }

 # ok, on peut bosser
 if (board) { p "\tProgramming board: "board }
 else       { p "\tProgramming all boards" }
 sock_put(dev,sprintf("PROG %d\n",board))

 p "\tBe patient....."
 p "\tHint: check messages on DSP serial line console of MASTER"
}'




#%IU%(hostname:port)
#%MDESC%
# Do not touch, needed by the "raleur"
#
def _icepap_check(dev) '{
 local  ans


 # Allo? Gaston? Tes la?
 sock_par(dev,"close")
 if(!sock_par(dev,"connect"))
 {
  _icepap_err
  print "communication program not running on MASTER"
  print "Hint: reset MASTER or run \"icepap_sock\" on ",dev
  exit
 }
 sock_par(dev,"timeout",ICEPAP_TIMEOUT)
 ans=_icepap_query(dev,"","?_SOCKPING")
 if(ans != "OK") {
  _icepap_err
  print "bad response from MASTER"
  print "Hint: reset MASTER or run \"icepap_sock\" on ",dev
  exit
 }

}'


#%IU%(hostname:port)
#%MDESC%
#
def _icepap_close(dev) '{
 sock_put(dev,"_SOCKCLOSE\n");
 sock_par(dev,"close");
}'



#%IU%
#%MDESC%
#
def _icepap_err '{
 tty_cntl("md")
 printf("ICEPAP ERROR: ")
 tty_cntl("me")
}'


#%IU%
#%MDESC%
#
def _icepap_warn '{
 tty_cntl("md")
 printf("ICEPAP WARNING: ")
 tty_cntl("me")
}'


#%IU%(num, status)
#%MDESC%
#
def _icepap_postmove(num) '{
 global ICEPAP[]
 local mne

 mne = motor_mne(num)
 if(ICEPAP[mne]["wasmoving"] == 0) { return }

 ICEPAP[mne]["wasmoving"] = 0 
 user_icepap_postmove_one num
}'


#%IU%(num, status)
#%MDESC%
# Decode stop reason for the driver status given.
# No action, print only for diagnostic
#
def _icepap_stopcode(num, sta) '{
 global ICEPAP[]
 local  stop_code
 local  mne

 # Stop code implemented on bits 14-17 
 stop_code = (sta & ((1<<14)|(1<<15)|(1<<16)|(1<<17))) >> 14
 
 # Normal case is code 0
 if(!stop_code) { return(0) }

 # Ignore limitswitches
 if((stop_code==3) || (stop_code==4)) { return(0) }

 # Treat the stop code only once
 mne = motor_mne(num)
 if(stop_code == ICEPAP[mne]["stop_code"]) { return(1) }
 ICEPAP[mne]["stop_code"]=stop_code

 # Abnormal stop case
 _icepap_warn
 printf("\"%s\": abnormal stop condition: %s\n", \
   mne, _icepap_stopcode2str(mne, stop_code))
 return(1)

}'


#%IU%(mne, stop_code)
#%MDESC%
# Returns a string describing the given stop code for the given motor.
#
def _icepap_stopcode2str(mne, stop_code) '{
 global ICEPAP[]
 global ICEPAP_STOPCODE[]
 local  dev

 
 if(stop_code in ICEPAP_STOPCODE) {
   return(ICEPAP_STOPCODE[stop_code])
 }

 # After firmware release 3.03 the stop code bit has been
 # reused for other stop conditions that can be retrieved 
 # with a new command
 dev = ICEPAP[mne]["dev"]
 if((stop_code ==  5) && (ICEPAP[dev]["hrev"] < 3)) {
   return("closed loop settling timeout")
 }

 if(stop_code ==  5) {
   local addr ans
   local ext_stop_code

   # NOTE MP 22thNov2016: 
   # currently there is no way to get stop code for a linked axis
   if(ICEPAP[mne]["linked"] == "YES") {
     return("use \"linkedstat\" for details")
   }

   addr = ICEPAP[mne]["addr"]
   ans  = _icepap_query(dev, addr, "?STOPCODE")

   if(index(cmd_ans,"ERROR")) {
     return("close loop settling timeout")
   }

   if(sscanf(ans, "%x", ext_stop_code) != 1) {
     return("unknown (error on ?STOPCODE)")
   }

   # the four first bits are repeating the information already
   # present in the stop code bits of the status
   ext_stop_code  &= 0x00F0
   ext_stop_code >>= 4
   if(ext_stop_code ==  1) {
     return("close loop settling timeout") 
   }
   if(ext_stop_code ==  2) {
     return("external hold signal active") 
   }
 }

 return("unknown")
 
}'

#%IU%(dev,addr,cmd)
#%MDESC%
#
def _icepap_wr(dev, addr, cmd) '{
 local str
 global ICEPAP_HISTORY[]


 if(addr != "" && addr != "#") str=addr":"cmd; else str=cmd;
 if (addr == "#") str = "#" str
 icepap__debug dev ":==>" str
 _icepap_hist_add(dev, str)

 str=str"\n"
 sock_par(dev,"flush")
 sock_put(dev,str);
}'


#%IU%(ans)
#%MDESC%
# Returns the last valid char of the IcePAP answer.
# The terminator chars are ignored.
#
def _icepap_lastchar(ans) '{
 local c
 local idx

 for(idx=length(ans);idx;idx--) {
  c=substr(ans, idx, 1)
  if(c!="\r" && c!="\n")
   break;
 }

 return(c)
}'

#%IU%(dev,addr,cmd)
#%MDESC%
#
def _icepap_wrrd(dev,addr,cmd) '{
 local ret
 local ans
 local mline 
 local eoa
 
 ret = ""

 _icepap_wr(dev,addr,cmd)
 # MP: TO BE REMOVED when all firmwares will be >=2.0
 if(ICEPAP[dev]["hrev"]<2) {
  # NOTE MP 10Apr14: warning, long answers could be truncated
  ret=sock_get(dev)
 } else {
  # handle multilines answers
  for(mline=0;;) {
   ans=sock_get(dev, "\n");

   if(_icepap_lastchar(ans) == "\$") {
    mline = (mline == 0)
   }
   
   # little optimization for most common case, non multilines answers
   if(ret=="") {
    ret = ans 
   } else {
    ret = sprintf("%s%s", ret, ans)
   }

   if(mline == 0) { 
    break 
   }
  }
 }
 icepap__debug dev ":<==" ret

 _icepap_hist_append(ret)

 return(ret)
}'


#%IU%(dev,addr,cmd,silent)
#%MDESC%
#
def _icepap_query(hn,addr,cmd,silent) '{
  local dev
  local ans[]
  local cmd_ans
  local len_ans 
  local lc
  local n
  local ret
  local i


  
  ret=""
  dev=sprintf("%s%s",hn, index(hn,":")?"":":5000")

  cmd_ans=_icepap_wrrd(dev,addr,cmd)

  for(;;) {
   len_ans=length(cmd_ans)
   lc = substr(cmd_ans,len_ans,1)
   if((lc=="\n") || (lc=="\r")) {
    cmd_ans=substr(cmd_ans,0,len_ans-1)
    continue
   }
   break
  }

  n=split(cmd_ans,ans)
  if(n<1) {
   if(!silent) { 
    _icepap_err
    printf("bad answer from IcePAP\n") 
   }
   return(cmd_ans)
  }
  
  if(index(ans[1],"ERROR")) {
   if(!silent) { 
    _icepap_err
    printf("%s\n",cmd_ans) 
   }
   return(cmd_ans)
  }

  # NOTE MP 13Dec18: some commands are still not ISG protocol compliant
  # not echoing the command before the answer itself,
  # for instance the ?_sockping 
  ret=(n>1)?substr(cmd_ans,index(cmd_ans," ")+1):""
  ret=substr(cmd_ans,index(cmd_ans," ")+1)

  return(ret)
}'


#%IU%()
#%MDESC%
# Temporary patch
#
def _icepap_patch(dev, addr) '{
  local slave
  local dest

  print "WARNING: icepap ",dev,"MASTER DSP being reset (",addr,")"

  # Reset the MASTER DSP only, DRIVERs not affected
  _icepap_wr(dev,"","_DSPRST")
 
  # Give a minimum time to DSP to reset (and to DRIVERs to come back)
  # or check that it is alive
  sleep(3)

  # Reaffect a CAN address to the DRIVER
#  slave=int(addr/10)*10
  
  # If the DRIVER is in the MASTER rack, the CAN address is already given
#  if(slave != 0) 
#  { 
#   _icepap_wr(dev,slave,"RESET") 
#   sleep(10)
#  }

}'

#%IU%()
#%MDESC%
# Temporary email after patch
#
def _icepap_patch_email() '{
  dest = "perez@esrf.fr jclement@esrf.fr"
  unix(sprintf("echo \"%s\" |mailx -s \"ERROR on %s session %s\" %s",\
   _icepap_hist_dump(),\
    SPECBL,\
    SPEC,\
    dest))
}'

#%IU%(message)
#%MDESC%
# Send an email with the specificied patch
#
def _icepap_email(subject, str) '{
  dest = "perez@esrf.fr jclement@esrf.fr"
  unix(sprintf("echo \"%s\" |mailx -s \"%s: session: \\\"%s\\\"\" %s",\
    str,\
    subject,\
    SPEC,\
    dest))
}'


#%IU%()
#%MDESC%
# Init the ICEPAP history if needed
#
def _icepap_hist_init() '{
 global ICEPAP_HISTORY[]

 if(!("size" in ICEPAP_HISTORY)) {
  ICEPAP_HISTORY["size"]=1000
  ICEPAP_HISTORY["end"] =0
 }
}'


#%IU%(dev,cmd)
#%MDESC%
# Add to the ICEPAP history an entry
#
def _icepap_hist_add(dev, cmd) '{
 global ICEPAP_HISTORY[]
 local  n
 local  tab

 tab = "   "
 _icepap_hist_init()
 n = ICEPAP_HISTORY["end"] + 1
 if(n >= ICEPAP_HISTORY["size"]) { n = 0}
 
 ICEPAP_HISTORY[n] = sprintf("%s%s%s%s%s",date(),tab,dev,tab,cmd)
 ICEPAP_HISTORY["end"] = n 
}'

#%IU%(str)
#%MDESC%
# Append a string to the last line in the ICEPAP history
#
def _icepap_hist_append(str) '{
 global ICEPAP_HISTORY[]
 local  n

 n = ICEPAP_HISTORY["end"]
 
 ICEPAP_HISTORY[n] = sprintf("%s -> %s",ICEPAP_HISTORY[n], str)
}'


#%IU%()
#%MDESC%
# Return a string with  the history of commands sent to ICEPAP systems
#
def _icepap_hist_dump() '{
 global ICEPAP_HISTORY[]
 local  n
 local  i
 local  s
 local  ret

 ret = ""
 _icepap_hist_init()
 s = ICEPAP_HISTORY["size"]
 for(n=ICEPAP_HISTORY["end"];(n in ICEPAP_HISTORY) && (i<s);i++)
 {
  ret = sprintf("%s%s\n",ret,ICEPAP_HISTORY[n])
  n--
  if(n<0) { n = s - 1 }
 }
 return ret

}'

#%UU%
#%MDESC%
# Print out the history of commands sent to ICEPAP systems
#
def icepap_history '{
 print _icepap_hist_dump()
}'




#%IU%(motor_mne)
#%MDESC%
# Returns non null if the given motor mne is not an IcePAP one
# 
def _icepap_ismot(mne, silent) '{
  local num

  if((num=motor_num(mne)) == -1) {
    if(!silent) {
      _icepap_err
      printf("Invalid motor \"%s\"\n", mne)
    }
    return(-1)
  }
  if(motor_par(num, "device_id") != "icepap") {
    if(!silent) {
      _icepap_err
      printf("Not an IcePAP motor: \"%s\"\n", mne)
    }
    return(-1)
  }

  return(0)
}'






#
# ----------------------- test macros -------------------------
#


#%IU%(dev,src_addr,dst_addr)
#%MDESC%
# Will copy the source driver configuration to the destination one
# (bypass of icepapcms)
# ex: ("iceid207",11,25)
#
def icepap_duplicate_conf(dev,src_addr,dst_addr) '{
  local src_conf
  local src_arr[]
  local n i
  local dst_cmd
  local dst_err

  if(!index(dev,":")) { dev = sprintf("%s:5000",dev) }
  src_conf = _icepap_wrrd(dev,src_addr,"?CFG")
  n = split(src_conf,src_arr,"\r\n") - 1
  dst_addr = "#" dst_addr
  for(i=1;i<n;i++) {
     dst_cmd = sprintf("_cfg %s", src_arr[i])
     dst_err = _icepap_wrrd(dev,dst_addr,dst_cmd)
     if(!index(dst_err,"OK")) {
        p "ERROR: sending command: "dst_cmd" error: "dst_err
        return(-1)
     }
  }

  dst_cmd = "_CFG SAVE"
  dst_err = _icepap_wrrd(dev,dst_addr,dst_cmd)
  if(!index(dst_err,"OK")) {
     p "ERROR: sending command: "dst_cmd" error: "dst_err
     return(-1)
  }

  # normal end
  p "ICEPAP configuration of driver "dst_addr" updated"
  return(0)
}'






#
# ----------------------- linked axis -------------------------
#

#%IU%_lvcheck_mne(virtual_axis_mne)
#%MDESC%
# Returns non null if the given axis mnemonic is not a valid virtual axis
# composed of real linked axes.
# 
def _lvcheck_mne(mne, silent) '{
  local num

  if(_icepap_ismot(mne, silent)) {
     if(!silent) {
        _icepap_err
        print "invalid motor \""mne"\", not configured" 
     }
     return(-1)
  }

  num = motor_num(mne)
  if(ICEPAP[mne]["linked"] != "YES") {
     if(!silent) {
       _icepap_err
       print "invalid motor \""mne"\", not a virtual axis of linked axes"
     }
     return(-1)
  }
  return(0)
}'


#%IU%_lrcheck_mne(real_axis_mne)
#%MDESC%
# Returns non null if the given axis mnemonic is not a real linked axis
# member of a virtual axis.
# 
def _lrcheck_mne(mne, silent) '{

  # Update global information on virtual axes
  _linkedstat()

  if(ICEPAP[mne]["linked_member"] != "YES") {
    if(!silent) {
      _icepap_err
      print "invalid motor \""mne"\", not a linked axis" 
    }
    return(-1)
  }
  return(0)
}'


#%UU% virtual_axis [virtual_axis...]
#%MDESC%
# Display information about the given virtual axis configured 
# as a motor in the current SPEC session.
# The list of real axes linked together should not be configured 
# but can be accessed through dedicated commands (linkedmv, linkedhome, etc).
#
def linkedstat '{
  global ICEPAP[]
  local  tt[] axes n 
  local  mne i
  local  wch str
  local  stpsz


  # Retrive the list of axes
  if($#<1) {
     print "Usage: $0 virtual_axis"
     exit
  }

  # Minimum checks on arguments
  n = split("$*", tt)
  for(i=0;i<n;i++) {
    mne = tt[i]
    if(_lvcheck_mne(mne)) { exit }
    axes[i] = motor_num(mne)
  }


  # Print out information
  wch = 20
  for(i=0;i<n;i++) {
    local j m tt
    local nam

    num = axes[i]
    mne = motor_mne(num)

    if(_linkedstat(num)) {
      exit
    }

    stpsz = motor_par(mne, "step_size")

    printf("%*s : %s\n",wch,"Virtual axis",mne)
    printf("%*s : %s\n",wch,"Power is",motor_par(num,"power")?"ON":"OFF")
    printf("%*s : %s\n",wch,"Real linked axes",ICEPAP[mne]["linked_names"])
    printf("%*s : %s\n",wch,"Indexer steps",ICEPAP[mne]["pos_stps"])
    printf("%*s : %s\n",wch,"Indexer in mm",ICEPAP[mne]["pos_stps"]/stpsz)


    m = split(ICEPAP[mne]["linked_names"], tt)
    for(j=0;j<m;j++) {
      nam = tt[j]
      printf("\n%*s : %s\n",wch,"Real axis",nam)
      printf("%*s : %s\n",wch,"Home switch",ICEPAP[nam]["homesw"])
      printf("%*s : %s\n",wch,"Homing",ICEPAP[nam]["homing"])
      printf("%*s : %s\n",wch,"Control encoder",ICEPAP[nam]["ctrlenc"])
      printf("%*s : %s\n",wch,"Indexer steps",ICEPAP[nam]["pos_stps"])
      printf("%*s : %s\n",wch,"Encoder steps",ICEPAP[nam]["enc_stps"])
      printf("%*s : %s\n",wch,"Homepos steps",ICEPAP[nam]["homepos_stps"])
      printf("%*s : %s\n",wch,"Indexer in mm",ICEPAP[nam]["pos_stps"]/stpsz)
      printf("%*s : %s\n",wch,"Encoder in mm",ICEPAP[nam]["enc_stps"]/stpsz)
      printf("%*s : %s\n",wch,"Closed loop",ICEPAP[nam]["pcloop"])

      stop_code = ICEPAP[nam]["stop_code"]
      printf("%*s : %s (%s)\n",wch,"Stop code", \
         stop_code, _icepap_stopcode2str(nam, stop_code))
    }
  }
}'



#%IU%(linked_axis)
#%MDESC%
# Update the global array ICEPAP[] with information about the given 
# virtual axis. Returns non null if an error occured.
#
def _linkedstat(num) '{
  global ICEPAP[]
  local  tt[] ttt[] axes n 
  local  mne i
  local  dev 
  local  ans
  local  nam str
  local  tnum err
  local  silent
  local  nums[] nnum j
  local  addr stop_code sta


  silent = 1

  # If no motor has been given try to guess all virtual axis configured
  nnum   = 0
  if(whatis("num") & 0x08000000) {
    for(i=0;i<MOTORS;i++) {
       if(_lvcheck_mne(motor_mne(i), silent)) { continue }
       nums[nnum++] = i
    }
  } else {
    nums[nnum++] = num
  }

  for(j=0;j<nnum;j++) {

  # Minimum check on argin
  mne = motor_mne(nums[j])
  if(motor_num(mne) == -1) {
    _icepap_err
    print "invalid motor \""mne"\""
    return(-1)
  }

  # Retrieve information to access the real axes
  dev = ICEPAP[mne]["dev"]
  ans = _icepap_query(dev,"",sprintf("?POS %s", mne))
  ICEPAP[mne]["pos_stps"] = int(ans)

  # Get the list of real axes linked
  n   = split(ICEPAP[mne]["linked_addr"], tt)
  str = ""
  for(i=0;i<n;i++) {

      # Real axis address on system
      addr = tt[i]

      # Get real axis name if any
      nam = _icepap_query(dev,addr,"?NAME")

      # Otherwise invent a name for the real axis 
      if((nam == "") || (nam=="?NAME")) { nam = sprintf("%s%d",mne,i+1) }

      # In theory it is not possible to have the real IcePAP motor 
      # configured in current SPEC session but another motor could have 
      # the name name
      for(;;) {
        err = 0
        if((tnum=motor_num(nam)) == -1)     { break }
        if(motor_par(tnum, "disable") == 1) { break }
  
        # Allow to have the real motor configured 
        err = 1
        if(motor_par(tnum, "device_id") != "icepap") { break }
        if(!index(dev, motor_par(tnum, "address")))  { break }
        taddr  = motor_par(tnum,"module")*10
        taddr += motor_par(tnum,"channel")
        if(int(taddr) !=  int(addr))                 { break }

        # We are sure that the configured and linked axes are the same
        _icepap_warn
        p "linked axis \""nam"\" configured as normal motor"

        err = 0
        break
      }
      if(err){
        _icepap_err
        print "conflict between configured and linked axis \""nam"\""
        return(-1)
      }

      # Update globals
      str = sprintf("%s%s ", str, nam)
      ICEPAP[mne]["linked_n"]     += 1
      ICEPAP[nam]["linked_name"]   = mne
      ICEPAP[nam]["linked_member"] = "YES"
      ICEPAP[nam]["dev"]           = dev
      ICEPAP[nam]["addr"]          = addr

      # Check if an home switch is configured on this axis
      ans = _icepap_query(dev,addr,"?CFG HOMESRC")
      ICEPAP[nam]["homesw"]        = index(ans,"NONE")?"NO":"YES"
    
      # Check if a control encoder is configured on this axis
      ans = _icepap_query(dev,addr,"?CFG CTRLENC")
      ICEPAP[nam]["ctrlenc"]       = index(ans,"NONE")?"NO":"YES"

      # Get positions in steps
      ans = _icepap_query(dev,addr,"?POS")
      ICEPAP[nam]["pos_stps"] = int(ans)
      ans = _icepap_query(dev,addr,"?POS MEASURE")
      ICEPAP[nam]["enc_stps"] = int(ans)

      # Get homing information
      ans = _icepap_query(dev,addr,"?HOMEPOS", silent)
      ICEPAP[nam]["homepos_stps"] = index(ans, "ERROR")?"unavailable":int(ans)
      ans = _icepap_query(dev,addr,"?HOMESTAT", silent)
      split(ans, ttt)
      ICEPAP[nam]["homing"] = ttt[0]

      # Get information on closed loop
      ans=_icepap_query(dev,addr,"?PCLOOP")
      ICEPAP[nam]["pcloop"] = (index(ans,"ON"))?"ON":"OFF" 

      # Get information on how the last motion finished
      ans = _icepap_query(dev, "", sprintf("?FSTATUS %d", addr))
      sscanf(ans, "%x", sta)
      stop_code = (sta & ((1<<14)|(1<<15)|(1<<16)|(1<<17))) >> 14
      ICEPAP[nam]["stop_code"] = stop_code
    }
    ICEPAP[mne]["linked_names"] = str

  }

  # Normal end
  return(0)
}'


#%UU% virtual_axis position
#%MDESC%
# Synchronizes all real linked axes members of the given virtual axis
# to the give position. No motion will take place.
# The position is given in user units of the virtual axis.
#
def linkedsync '{
  local  mne 
  local  pos


  # Retrieve the arguments
  if($#<2) {
     print "Usage: $0 motor position"
     exit
  }
  mne = "$1"
  pos =  $2

  # Minimum checks on arguments
  if(_lvcheck_mne(mne)) { exit }
 
  # Launch the synchronization
  _linkedsync(mne, pos)
}'


#%IU%(virtual_axis_mne, position)
#%MDESC%
# Synchronizes all real linked axes members of the given virtual axis
# to the give position. No motion will take place.
# The position is given in user units of the virtual axis.
# Returns non null if an error occured.
#
def _linkedsync(mne, p1) '{
  global ICEPAP[]
  local  dev
  local  num
  local  pos
  local  ans
  local  cmd
  local  par


  # Update global information on virtual axes
  _linkedstat()

  # Retrieve information on axis
  dev  = ICEPAP[mne]["dev"]
  num  = motor_num(mne)

  # Calculate the target position in steps
  pos = icepap_round(p1*motor_par(num,"step_size"))

  # Force position on all real linked axes
  cmd = sprintf("#POS %s %d", mne, pos)
  ans = _icepap_query(dev,"",cmd)
  if (ans != "OK")
  {
   _icepap_err
   printf("unable to force position on real linked axes of: \"%s\"\n",mne)
   return(-1)
  }


  # Force motion parameters on all real linked axes
  par  = motor_par(num, "acceleration")/1000
  ans  = _icepap_query(dev,"#",sprintf("ACCTIME %s %f",mne,par))
  if(index(ans,"ERROR"))
  {
   _icepap_err
   printf("unable set acceleration for: \"%s\"\n",mne)
   return(-1)
  }
  par  = motor_par(num, "velocity")
  ans  = _icepap_query(dev,"#",sprintf("VELOCITY %s %f",mne,par))
  if(index(ans,"ERROR"))
  {
   _icepap_err
   printf("unable set velocity for: \"%s\"\n",mne)
   return(-1)
  }

  
  # Reset control encoder
  # TODO: if any?
  ans  = _icepap_query(dev,"#",sprintf("CTRLRST %s",mne))
  if(index(ans,"ERROR"))
  {
   _icepap_err
   printf("unable reset control encoder for: \"%s\"\n",mne)
   return(-1)
  }

  # switch power on (should re-enable the closed loop)
  if(motor_par(num,"power",1) == 0) 
  {
   _icepap_err
   printf("unable to switch power on: \"%s\"\n",mne)
   return(0)
  }
   
  # NOTE: from "help motors": position discrepancies between spec and the
  # motor hardware will be silently resolved in favor of the hardware
  read_motors(0x06)


  # Normal end
  return(0)
}'




#%UU% ["strict"] real_axis "home+"|"home-" [[real_axis "home+"|"home-"]...]
#%MDESC%
# Launch a homing sequence on the given list of real linked axes. 
# If the strict option is given then the first real linked axis that 
# finds its home switch will stop the homing sequence of the other axes.
# The macros does not wait for the end of the homing sequence.
#
def linkedhome '{
  local  mne 
  local  sen
  local  stc
  local  tt[] n i
  local  arg


  # Retrieve the arguments
  if($#<2) {
     print "Usage: $0 [\"strict\"] motor \"home+\"|\"home-\" ..."
     exit
  }
  stc = ("$1" == "strict")?1:0

  n = split("$*",tt)
  for(i=stc, arg=""; i<n; i+=2) {
    mne = tt[i]
    sen = tt[i+1]

    # Minimum checks on arguments
    if(_lrcheck_mne(mne)) { exit }
    if((sen != "home+") && (sen != "home-")) { 
      _icepap_err
      printf("un-supported home or limit search \"%s\"\n", sen)
      exit
    }
    arg = sprintf("%s%s %s ",arg, mne, sen)
  }

  # Launch the homing sequence
  _linkedhome(arg, stc?"strict":"")
}'



#%IU%("mne "home+"|"home-" [[mne "home+"|"home-"]...]", mode)
#%MDESC%
# Launch a homing sequence on the given list of real linked axes. 
# If the mode option is set to "strict" then the first real linked axis that 
# finds its home switch will stop the homing sequence of the other axes.
# The global ICEPAP[] will then contains the name and the home
# position of this real axis.
# The macros does not wait for the end of the homing sequence.
# Returns non null if an error occured.
#
def _linkedhome(arg, mode) '{
  global ICEPAP[]
  local  lim
  local  dev
  local  num
  local  ans
  local  cmd1 cmd2 
  local  tt[] n i


  # Update global information on virtual axes
  _linkedstat()

  # The homing command can not be launched on the virtual axis but
  # only on the real linked axes.
  # The STRICT option is to stop all linked axes if any stops.
  cmd1 = "#DISPROT ALL"
  cmd2 = sprintf("#HOME %s",(mode=="strict")?"STRICT":"")

  # Retrieve the arguments
  n = split(arg, tt)
  for(i=0; i<n; i+=2) {
    mne  = tt[i]
    sen  = tt[i+1]
    lim  = (sen=="home-")?"-1":"+1"

    addr = ICEPAP[mne]["addr"]
    dev  = ICEPAP[mne]["dev"]
  
    cmd1 = sprintf("%s %d",    cmd1, addr)
    cmd2 = sprintf("%s %d %s", cmd2, addr, lim)
  }


  # Launch the homing sequence
  ans  = _icepap_query(dev,"",sprintf("%s ; %s", cmd1, cmd2))
  if(index(ans,"ERROR"))
  {
   _icepap_err
   p "unable to dis-protect axes"
   return(-1)
  }
  ans = sock_get(dev,"\n")
  if (!index(ans,"OK"))
  {
   _icepap_err
   p "homing sequence launch failed"
   p ans
   return(-1)
  }


  # Normal end
  return(0)
}'



#%UU% real_axis posititon [[real_axis posititon] ...]
#%MDESC%
# Move the specified real linked axes to absolute positions.
# The positions are given in user units of the virtual axis of which
# the real axes are members.
#
def linkedmv '{
  local  tt[] i n 
  local  mne 
  local  pos


  # Retrieve the arguments
  n = split("$*", tt)
  if(($#<2) || (n%2)) {
     print "Usage: $0 motor position [[motor position] ...]"
     exit
  }

  # Minimum checks on arguments
  for(i=0;i<n;i+=2) {
    mne = tt[i]
    pos = tt[i+1]

    if(_lrcheck_mne(mne)) { exit }
  }

  # Launch the motion
  _linkedmv("$*")
}'


#%UU% real_axis relative-posititon [[real_axis relative-posititon] ...]
#%MDESC%
# Move the specified real linked axes to relative positions.
# The positions are given in user units of the virtual axis of which
# the real axes are member.
#
def linkedmvr '{
  local  tt[] i n 
  local  mne 
  local  pos


  # Retrieve the arguments
  n = split("$*", tt)
  if(($#<2) || (n%2)) {
     print "Usage: $0 motor relative-position [[motor relative-position] ...]"
     exit
  }

  # Minimum checks on arguments
  for(i=0;i<n;i+=2) {
    mne = tt[i]
    pos = tt[i+1]

    if(_lrcheck_mne(mne)) { exit }
  }

  # Launch the motion
  _linkedmvr("$*")
}'



#%IU%(real_axis_mne, relative_position)
#%MDESC%
def _linkedmvr(arg) '{

  # Launch the motion
  _linkedmv(arg, 1)
}'



#%IU%("mne posititon [[mne position]...]")
#%MDESC%
# Move the specified real linked axes to absolute positions.
# Returns non null if an error occured.
#
def _linkedmv(arg, rel) '{
  global ICEPAP[]
  local  mne
  local  p1
  local  ans
  local  addr
  local  dev
  local  sta
  local  pos  stps
  local  vmne vnum
  local  cmd1 cmd2 
  local  sta  ret
  local  tt[] i n 
  local  addrs[] nsta
  local  sarg


  # Update global information on virtual axes
  _linkedstat()

  # Prepare the commands
  cmd1 = "#DISPROT ALL"
  cmd2 = "#MOVE"

  # For each real axis
  n    = split(arg, tt)
  nsta = 0
  for(i=0;i<n;i+=2) {
 
    # Retrieve the arguments
    mne  = tt[i]
    pos  = tt[i+1]

    # Retrieve information on axis
    addr = ICEPAP[mne]["addr"]
    dev  = ICEPAP[mne]["dev"]
    vmne = ICEPAP[mne]["linked_name"]
    vnum = motor_num(vmne)
    
    # Prepare stop command 
    sarg = dev"="

    
    # Minimum check that motion is possible
    sta = 0
    ans = _icepap_query(dev,"",sprintf("?FSTATUS %d",addr))
    if(sscanf(ans,"%x", sta) != 1) 
    {
     _icepap_err
     printf("unable to get status for: \"%s\"\n",mne)
     return(-1)
    }
    # Get ready status
    if(!(sta & (1<<9)))
    {
     _icepap_err
     printf("driver is not ready for: \"%s\"\n",mne)
  
     # temporary: for debug purpose only
     _icepap_print_info(dev,addr,mne)
     return(-1)
    }

    # Calculate the target position in steps
    stps = icepap_round(pos*motor_par(vnum,"step_size"))


    # Handle relative motions
    if(rel) {
      local apos

      # Get current position
      ans= _icepap_query(dev,"",sprintf("?FPOS %s",addr))
      if(sscanf(ans,"%d",apos) != 1) {
        _icepap_err
        printf("unable to get position for: \"%s\"\n",mne)
        return(-1)
      }
     stps += apos
    }

    cmd1 = sprintf("%s %d", cmd1, addr)
    cmd2 = sprintf("%s %d %d", cmd2, addr, stps)
    sarg = sprintf("%s %d", sarg, addr)

    # Prepare the list of real axis to poll
    addrs[nsta++] = addr
  }


  # Handle the <ctrl-c>
  cdef("cleanup_once", sprintf("_linked_stop(\"%s\")\n", sarg), "_linked_icepap")

  # Launch the motion
  ans = _icepap_query(dev,"",sprintf("%s ; %s", cmd1, cmd2))
  if (ans != "OK")
  {
   _icepap_err
   p "unable to dis-protect axis"
   _linked_clean_cleanup()
   return(-1)
  }
  ans = sock_get(dev,"\n")
  if (!index(ans,"OK"))
  {
   _icepap_err
   p "unable to start motion for"
   p ans
   _linked_clean_cleanup()
   return(-1)
  }



  # Wait for the end of the motion
  for(nret=0x02;nret;sleep(UPDATE)) {
    for(i=0,nret=0;i<nsta;i++) {
      addr = addrs[i]
      if((addr = addrs[i]) == "") { continue }
      ret  = 0
      ans  = _icepap_query(dev,"",sprintf("?FSTATUS %s",addr))
      if(sscanf(ans,"%x", sta) != 1) 
      {
       _icepap_err
       printf("unable to get status for: \"%s\"\n",mne)
       _linked_clean_cleanup()
       return(-1)
      }

      if((sta & (1<<10)) || (sta & (1<<11))) ret |= 0x02;
      else if(!(sta & (1<<9)) && (sta & (1<<23))) ret |= 0x02; 

      # get limitswitches status
      if(sta & (1<<19)) ret |= 0x04;
      if(sta & (1<<18)) ret |= 0x08;

      # check stop code if not moving
      if(!(ret & 0x02)) {
        _icepap_stopcode(num, sta)
        delete addrs[i]
      }

      nret |= ret
    }
  }

  # Normal end
  _linked_clean_cleanup()
  return(0)
}'

#%IU%()
#%MDESC%
# Needed to avoid side effects on <ctrl-C>
#
def _linked_clean_cleanup() '{
  cdef("cleanup_once", "", "_linked_icepap", "delete")
}'


#%IU%("dev=addr [addr...]")
#%MDESC%
# Stop the motion on the speficied axis identified by their address
#
def _linked_stop(sarg) '{
  local dev
  local args[]
  
  if(split(sarg, args, "=") != 2) {
    _icepap_err
    printf("major internal error on _linked_stop(\"%s\")\n", sarg)
    return(-1)
  }
  dev = args[0]
  
  printf("ICEPAP WARNING: Aborting motion on \"%s\" for axes:%s\n", \
    dev, args[1])

  _icepap_wr(dev,"",sprintf("STOP %s", args[1]))

  return(0)
}'


#%UU% real_axis [real_axis ...]
#%MDESC%
# In case of closed error on a real linked axis, this macro
# has to be called to resynchronize the motor and its encoder.
# The power will be also put back.
#
def linkedresetclosedloop '{
  local  tt[] i n 
  local  mne 
  local  pos


  # Retrieve the arguments
  n = split("$*", tt)
  if(n == 0) {
     print "Usage: $0 motor [motor ...]"
     exit
  }

  # Minimum checks on arguments
  for(i=0;i<n;i++) {
    mne = tt[i]

    if(_lrcheck_mne(mne)) { exit }
  }

  # Acces to the real axis
  _linkedresetclosedloop("$*")
}'


#%IU%("mne [mne ...]")
#%MDESC%
# Reset closed loop error on the specified real linked axes.
# Returns non null if an error occured.
#
def _linkedresetclosedloop(arg) '{
  global ICEPAP[]
  local  mne
  local  addr
  local  dev
  local  cmd 
  local  ans
  local  tt[] i n 


  # Update global information on virtual axes
  _linkedstat()

  # For each real axis
  n    = split(arg, tt)
  for(i=0;i<n;i++) {
 
    # Retrieve the arguments
    mne  = tt[i]

    # Retrieve information on axis
    addr = ICEPAP[mne]["addr"]
    dev  = ICEPAP[mne]["dev"]
    
    # Get current encoder position
    ans = _icepap_query(dev, addr, "?POS MEASURE")
    if(index(ans,"ERROR"))
    {
      _icepap_err
      printf("unable to read encoder for: \"%s\"\n",mne)
      return(-1)
    }

    # Set axis position to same value to remove closed loop discrepancy
    # NOTE MP 21Jul14: the axis command does not work with firmware 2.0
    # the system command must be used instead
    cmd = sprintf("DISPROT LINKED %s ; #POS %s %s", addr, addr, ans)
    ans = _icepap_query(dev, "", cmd)
    if(index(ans,"ERROR"))
    {
      _icepap_err
      printf("unable to reset closed loop error on: \"%s\"\n",mne)
      return(-1)
    }

    # Switch on closed loop on real axes
    cmd = sprintf("%s:DISPROT LINKED ; #%s:PCLOOP ON", addr, addr)
    ans = _icepap_query(dev, "", cmd)
    if(index(ans,"ERROR"))
    {
      _icepap_err
      printf("unable to switch on closed loop, error on: \"%s\"\n",mne)
      return(-1)
    }

    # Switch power on (should re-enable the closed loop)
    ans = _icepap_query(dev, "#" addr, "POWER ON")
    if(index(ans,"ERROR"))
    {
      _icepap_err
      printf("unable to switch power on for: \"%s\"\n",mne)
      return(-1)
    }
  }

}'



#%UU% real_axis [real_axis ...]
#%MDESC%
# Open the closed loop on the specified real linked axes.
# The power is not touched.
#
def linkedopenclosedloop '{
  local  tt[] i n 
  local  mne 
  local  pos


  # Retrieve the arguments
  n = split("$*", tt)
  if(n == 0) {
     print "Usage: $0 motor [motor ...]"
     exit
  }

  # Minimum checks on arguments
  for(i=0;i<n;i++) {
    mne = tt[i]

    if(_lrcheck_mne(mne)) { exit }
  }

  # Acces to the real axis
  _linkedopenclosedloop("$*")
}'


#%IU%("mne [mne ...]")
#%MDESC%
# Open the closed loop on the specified real linked axes.
# Returns non null if an error occured.
#
def _linkedopenclosedloop(arg) '{
  global ICEPAP[]
  local  mne
  local  addr
  local  dev
  local  cmd 
  local  ans
  local  tt[] i n 


  # Update global information on virtual axes
  _linkedstat()

  # For each real axis
  n    = split(arg, tt)
  for(i=0;i<n;i++) {
 
    # Retrieve the arguments
    mne  = tt[i]

    # Retrieve information on axis
    addr = ICEPAP[mne]["addr"]
    dev  = ICEPAP[mne]["dev"]
    
    cmd = sprintf("%s:DISPROT LINKED ; #%s:PCLOOP OFF", addr, addr)
    ans = _icepap_query(dev, "", cmd)
    if(index(ans,"ERROR"))
    {
      _icepap_err
      printf("unable to open closed loop error on: \"%s\"\n",mne)
      return(-1)
    }

  }

}'





#%UU% virtual_axis
#%MDESC%
# Synchronizes all real linked axes members of the given virtual axis
# to the give position. No motion will take place.
#
def linkedreset '{
  local  mne 
  local  pos


  # Retrieve the arguments
  if($#<1) {
     print "Usage: $0 motor"
     exit
  }
  mne = "$1"

  # Minimum checks on arguments
  if(_lvcheck_mne(mne)) { exit }
 
  # Launch the synchronization
  _linkedreset(mne)
}'


#%IU%(virtual_axis_mne)
#%MDESC%
# Synchronizes all real linked axes members of the given virtual axis
# to the give position. No motion will take place.
# Returns non null if an error occured.
#
def _linkedreset(mne) '{
  global ICEPAP[]
  local  dev
  local  num
  local  pos
  local  ans
  local  cmd
  local  par
  local  pos
  local  i n tt[]
  local  stop_code


  # Retrieve information on axis
  dev  = ICEPAP[mne]["dev"]
  num  = motor_num(mne)

  # Synchronize the real axes positions with the encoder ones
  icepap__debug "synchronize real axes positions with encoder ones"
  cmd = sprintf("#ESYNC %s", mne)
  ans = _icepap_query(dev,"",cmd)
  if (ans != "OK")
  {
   _icepap_err
   printf("unable to sync axes positions with encoders ones for: \"%s\"\n",mne)
   return(-1)
  }

  # Update global information on virtual axes
  _linkedstat()

  # Calculate the average real axes positions
  n   = split(ICEPAP[mne]["linked_names"], tt)
  pos = 0.0
  for(i=0;i<n;i++) {
    nam  = tt[i]
    pos += ICEPAP[nam]["pos_stps"]
  }
  pos /= n
  pos /= motor_par(num, "step_size")


  # Check how the real axes stopped
  for(i=0;i<n;i++) {
    nam  = tt[i]

    # Save time
    if((stop_code = ICEPAP[nam]["stop_code"]) == 0) {
       continue
    }

    # Limit activated
    if((stop_code ==  3) || (stop_code ==  4)) { 
      icepap__debug "axis \"" nam "\" stopped on limitswitch"
      pos  = ICEPAP[nam]["pos_stps"]
      pos /= motor_par(num, "step_size")

      # TODO: what to do if several axes touched their limitswitches?
      break
    }
  }

  
  # Re-align mechanically the real axes
  icepap__debug "move real axes to position: " pos "mm"
  cmd = ""
  for(i=0;i<n;i++) {
    nam  = tt[i]
    cmd  = sprintf("%s %s %f", cmd, nam, pos)
  }
  _linkedmv(cmd)


  # Put back the linked axis in operation
  _linkedsync(mne, pos)


  # Normal end
  return(0)
}'



#
# ----------------------- electronic cam -------------------------
#

constant _ECAM_SIGNAL "PULSE LOW HIGH"
constant _ECAM_SOURCE "AXIS MEASURE PARAM SHFTENC TGTENC CTRLENC ENCIN INPOS ABSENC MOTOR"


#%UU% motor source signal {pos_array}|{pos_first pos_last npos}
#%MDESC%
# Configure for the given motor source which kind of signal will be
# generated and at which positions. These ones can be given as
# an array of motor source positions or only as the beginning and
# ending positions with the number of positions.
# Example:
# short array listpos[10]
# array_op("fill", listpos, 100)
# ecamsetup m2 AXIS PULSE listpos
# ecamlist m2
# ecamlist m2 clear
# 
def ecamsetup '{
  global ICEPAP[]
  local  mne
  local  dev
  local  ans
  local  addr
  local  badconf
  local  bin_typ
  local  src_typ
  local  sig_typ


  # Minium check on arguments
  if($#<4) {
    _icepap_err
    print "Missing arguments"
    print "Usage: $0 source signal {pos_array}|{pos_first pos_last npos}"
    print "Source: " _ECAM_SOURCE
    print "Signal: " _ECAM_SIGNAL
    exit
  }
  bin_typ  = (whatis("$4") & 0x00010000)?1:0
  if(!bin_typ && ($#!=6)) {
    _icepap_err
    print "Missing first/last positions and number of positions"
    exit
  }

  # Minimum check on motor argument 
  mne = "$1"
  if(_icepap_ismot(mne)) { exit }


  # Minimum check on source argument 
  src_typ = icepap__toupper("$2")
  if(!index(_ECAM_SOURCE, src_typ)) {
    _icepap_err
    print "Wrong signal type, must be one of the following:"
    print "\""_ECAM_SOURCE"\""
    exit
  }

  # Minimum check on signal type argument 
  sig_typ = icepap__toupper("$3")
  if(!index(_ECAM_SIGNAL, sig_typ)) {
    _icepap_err
    print "Wrong signal type, must be one of the following:"
    print "\""_ECAM_SIGNAL"\""
    exit
  }

  # Check that there is at least one INFOA,B,C line configured
  dev  = ICEPAP[mne]["dev"]
  addr = ICEPAP[mne]["addr"]
  for(badconf=1, ch=asc("A");ch<=asc("C");ch++) {
    ans = _icepap_query(dev, addr, sprintf("?INFO%c",ch))
    if(index(ans, "eCAM")) {
      badconf = 0
      break
    }
  }
  if(badconf) {
    _icepap_err
    print "missing one INFOA,B,C line configured to ECAM. Hint: use IcePAPcms"
    return(-1)
  }

  # Call the corresponding setup
   _ecamsetup($1, src_typ, sig_typ, $4, $5, $6)
}'



#%UU% motor [clear]
#%MDESC%
# Print out current position list information for the given motor.
# Or remove already loaded position list.
# 
def ecamlist '{
  global ICEPAP[]
  local  mne
  local  dev
  local  addr
  local  cmd
  local  ans
  local  mod_typ
  local  tt[] l i n




  # Minium check on arguments
  if($#<1) {
    _icepap_err
    print "Missing arguments"
    print "Usage: $0 motor [clear]"
    exit
  }

  # Minimum check on motor argument 
  mne = "$1"
  if(_icepap_ismot(mne)) { 
    _icepap_err
    print "Not an IcePAP motor"
    exit 
  }

  # Retrieve access to the IcePAP axis
  dev  = ICEPAP[mne]["dev"]
  addr = ICEPAP[mne]["addr"]

  # Clear already loaded position list
  if($#>1) {
    opt = "$2"
    opt = icepap__toupper(opt)
    if(opt == "CLEAR") {
      print "Clearing position list defined for motor \"" mne "\""
      cmd = sprintf("ECAMDAT CLEAR")
      ans = _icepap_query(dev, "#" addr, cmd)
      if(index(ans, "ERROR")) {
        return
      }

      # avoid displaying error on emptied list
      return
    }
  }

  # Get global configuraiton 
  # Ex of answer: AXIS 0 1000 11
  cmd = sprintf("?ECAMDAT")
  ans = _icepap_query(dev, addr, cmd)
  if(index(ans, "ERROR")) {
    return
  }

  # Get the number of positions in the list
  n = split(ans, tt)
  if(sscanf(tt[n-1], "%d", npos) != 1) {
    _icepap_err
    print "Invalid answer from DSP, giving up"
    return
  }
  if(!npos) {
    print "No position list defined for motor \"" mne "\""
    print "Hint: use \"ecamsetup\""
    return
  }

  # Get positions 
  cmd = sprintf("?ECAMDAT %d 0", npos)
  ans = _icepap_query(dev, addr, cmd)
  if(index(ans, "ERROR")) {
    return
  }

  print "number of positions:", npos
  print "positions list     :"
  n = split(ans, tt, "\n")
  for(i=0;i<n;i++) {
    if(index(tt[i], "\$")) { 
      continue 
    }
    print tt[i] 
  }


}'


#%IU%(motor, source, signal, posbeg, posend, npos)
#%MDESC%
# Returns non null if an error occured.
# 
def _ecamsetup(num, source, signal, posbeg, posend, npos) '{
  global ICEPAP[]
  local  mne
  local  dev
  local  addr
  local  cmd
  local  ans
  local  bin_typ

  # No verification on parameters, must have been done in the calling macro
  mne  = motor_mne(num)
  dev  = ICEPAP[mne]["dev"]
  addr = ICEPAP[mne]["addr"]

  # Store information
  ICEPAP[mne]["ecam_src"] = source
  ICEPAP[mne]["ecam_sig"] = signal
  ICEPAP[mne]["ecam_bin"] = "NO"
  
  # Guess how the positions have been given
  bin_typ  = (whatis("posbeg") & 0x00010000)?1:0

  # Prepare the DSP command, even if source is optional, always give it
  if(!bin_typ) {
    cmd = sprintf("ECAMDAT %s %d %d %d", source, posbeg, posend, npos)
    ans = _icepap_query(dev, "#" addr, cmd)
    if(index(ans, "ERROR")) {
      return(-1)
    }
  } else {
    pos_typ = _icepap_array_type(posbeg)
    if(index(pos_typ, "ERROR")) {
      return(-1)
    }
    if((pos_typ!="DWORD") && (pos_typ!="FLOAT") && (pos_typ!="DFLOAT")) {
      _icepap_err
      print "unsupported position array type"
      return(-1)
    }
    cmd = sprintf("*ECAMDAT %s %s", source, pos_typ)

    # Send the binary data
    silent = 1
    ans = _icepap_send_array(dev, "#" addr, cmd, posbeg, silent)
    if(index(ans, "ERROR")) {
      print ans
      return(-1)
    }
  } 
 
  # Normal end
  return(0)
}'





#%UU% motor
#%MDESC%
# 
def ecamon '{ 
  # Minimum cheks
  if($# != 1) {
    print "Usage: $0 motor"
    exit
  }
  if(_icepap_ismot("$1")) { exit }

  # Action
  _ecamaction($1, "ON") 
}'

#%UU% motor
#%MDESC%
# 
def ecamoff '{ 
  # Minimum cheks
  if($# != 1) {
    print "Usage: $0 motor"
    exit
  }
  if(_icepap_ismot("$1")) { exit }

  # Action
  _ecamaction($1, "OFF") 
}'


#%IU%(motor, action)
#%MDESC%
# Swith "ON" of "OFF" the electronic cam on the given axis which must
# have been previously configured with "ecamdat" macro.
# Returns non null if an error occured.
# 
def _ecamaction(num, action) '{
  global ICEPAP[]
  local  mne
  local  dev
  local  addr
  local  cmd
  local  ans

  # No verification on parameters, must have been done in the calling macro
  mne  = motor_mne(num)
  dev  = ICEPAP[mne]["dev"]
  addr = ICEPAP[mne]["addr"]
  sig  = ICEPAP[mne]["ecam_sig"]

  # Check that some data have already been configured
  # WARNING: ?ECAMDAT does not return ERROR if not configuration already done
  # the command retursn unconsistant values. Dont know if these are
  # always the same, therfore do not use command ?ECAMDAT

  # Desactivate electronic CAM
  if(action == "OFF") {
    ans = _icepap_query(dev, "#" addr, "ECAM OFF")
    return((index(ans, "ERROR"))?-1:0)
  }

  # Enable electronic CAM
  if(action == "ON") {
    cmd = sprintf("ECAM %s", sig)
    ans = _icepap_query(dev, "#" addr, cmd)
    return((index(ans, "ERROR"))?-1:0)
  }

  # Abnormal end
  return(-1)
}'



#
# ----------------------- position list modes -------------------------
#

constant _ICELIST_MODE "NOCYCLIC CYCLIC"
constant _ICELIST_LTRACKSRC "SYNC ENCIN INPOS ABSENC TRIGGER"


#%UU% motor {cyclic|nocyclic} {pos_array}|{pos_first pos_last npos}
#%MDESC%
# Define for the given motor a list of positions that will be used
# cyclicelly or not.
# These positions can be given as
# an array of motor positions or as the beginning/ending positions 
# plus the total number of positions.
# The positions are given in steps.
#%BR% Example:
#%BR% long array listpos[11]
#%BR% array_op("fill", listpos, 100)
#%BR% poslistsetup m2 nocyclic listpos
#%BR% poslist m2
#%BR% poslist m2 clear
#%BR% poslistmv m2 2
# 
def poslistsetup '{
  global ICEPAP[]
  local  mne
  local  bin_typ
  local  mod_typ


  # Minium check on arguments
  if($#<3) {
    _icepap_err
    print "Missing arguments"
    print "Usage: $0 motor mode {pos_array}|{pos_first pos_last npos}"
    print "Mode : " _ICELIST_MODE
    exit
  }
  bin_typ  = (whatis("$3") & 0x00010000)?1:0
  if(!bin_typ && ($#!=5)) {
    _icepap_err
    print "Missing first/last positions and number of positions"
    exit
  }

  # Minimum check on motor argument 
  mne = "$1"
  if(_icepap_ismot(mne)) { 
    _icepap_err
    print "Not an IcePAP motor"
    exit 
  }

  # Minimum check on source argument 
  mod_typ = icepap__toupper("$2")
  if(!index(_ICELIST_MODE, mod_typ)) {
    _icepap_err
    print "Wrong list mode, must be one of the following:"
    print "\""_ICELIST_MODE"\""
    exit
  }


  # Call the corresponding setup
  _poslistsetup($1, mod_typ, $3, $4, $5)
}'




#%UU% motor [clear]
#%MDESC%
# Print out current position list information for the given motor.
# Or remove already loaded position list.
# 
def poslist '{
  global ICEPAP[]
  local  mne
  local  dev
  local  addr
  local  cmd
  local  ans
  local  mod_typ
  local  tt[] l i n
  local  opt




  # Minium check on arguments
  if($#<1) {
    _icepap_err
    print "Missing arguments"
    print "Usage: $0 motor [clear]"
    exit
  }

  # Minimum check on motor argument 
  mne = "$1"
  if(_icepap_ismot(mne)) { 
    _icepap_err
    print "Not an IcePAP motor"
    exit 
  }

  # Retrieve access to the IcePAP axis
  dev  = ICEPAP[mne]["dev"]
  addr = ICEPAP[mne]["addr"]

  # Clear already loaded position list
  if($#>1) {
    opt = "$2"
    opt = icepap__toupper(opt)
    if(opt == "CLEAR") {
      print "Clearing position list defined for motor \"" mne "\""
      cmd = sprintf("LISTDAT CLEAR")
      ans = _icepap_query(dev, "#" addr, cmd)
      if(index(ans, "ERROR")) {
        return
      }

      # avoid displaying error on emptied list
      return
    }
  }

  # Get global configuraiton 
  cmd = sprintf("?LISTDAT")
  ans = _icepap_query(dev, addr, cmd)
  if(index(ans, "ERROR")) {
    return
  }

  # First is the number of positions in the list
  split(ans, tt)
  if(sscanf(tt[0], "%d", npos) != 1) {
    _icepap_err
    print "Invalid answer from DSP, giving up"
    return
  }
  if(!npos) {
    print "No position list defined for motor \"" mne "\""
    print "Hint: use \"poslistsetup\""
    return
  }
  mod_typ = tt[1]

  # Get positions 
  cmd = sprintf("?LISTDAT %d 0", npos)
  ans = _icepap_query(dev, addr, cmd)
  if(index(ans, "ERROR")) {
    return
  }

  print "number of positions:", npos
  print "list usage         :", mod_typ
  print "positions list     :"
  n = split(ans, tt, "\n")
  for(i=0;i<n;i++) {
    if(index(tt[i], "\$")) { 
      continue 
    }
    print tt[i] 
  }


}'



#%IU%(motor, mode, posbeg, posend, npos) or (motor, mode, posarray)
#%MDESC%
# Define for the given motor a list of positions.
# Returns non null if an error occured.
# 
def _poslistsetup(num, mode, posbeg, posend, npos) '{
  global ICEPAP[]
  local  mne
  local  dev
  local  addr
  local  cmd
  local  ans
  local  bin_typ
  local  pos_typ
  local  silent

  # No verification on parameters, must have been done in the calling macro
  mne  = motor_mne(num)
  dev  = ICEPAP[mne]["dev"]
  addr = ICEPAP[mne]["addr"]

  # Store information
  ICEPAP[mne]["list_mode"] = mode
  
  # Guess how the positions have been given
  bin_typ  = (whatis("posbeg") & 0x00010000)?1:0

  # Prepare the DSP command, even if source is optional, always give it
  if(!bin_typ) {
    cmd = sprintf("LISTDAT %s %d %d %d", mode, posbeg, posend, npos)
    ans = _icepap_query(dev, "#" addr, cmd)
    if(index(ans, "ERROR")) {
      return(-1)
    }
  } else {
    pos_typ = _icepap_array_type(posbeg)
    if(index(pos_typ, "ERROR")) {
      return(-1)
    }
    if((pos_typ!="DWORD") && (pos_typ!="FLOAT") && (pos_typ!="DFLOAT")) {
      _icepap_err
      print "unsupported position array type"
      return(-1)
    }
    cmd = sprintf("*LISTDAT %s %s", mode, pos_typ)

    # Send the binary data
    silent = 1
    ans = _icepap_send_array(dev, "#" addr, cmd, posbeg, silent)
    if(index(ans, "ERROR")) {
      print ans
      return(-1)
    }
  } 

  # Normal end
  return(0)
}'


#%UU% motor index
#%MDESC%
# Move the given motor at the position corresponding to the given index
# within a pre-configured positions list.
# The index can be float, then the target position will be interpolated
# inbetween the two nearest pre-configured positions.
# The macro waits for the end of the motion.
# 
def poslistmv '{
  global ICEPAP[]
  local  mne
  local  idx
  local  silent

  # Minium check on arguments
  if($#!=2) {
    _icepap_err
    print "Missing arguments"
    print "Usage: $0 motor index"
    exit
  }

  # Minimum check on motor argument 
  mne = "$1"
  if(_icepap_ismot(mne)) { 
    _icepap_err
    print "Not an IcePAP motor"
    exit 
  }
  if(sscanf("$2","%f",idx) != 1) {
    _icepap_err
    print "Wrong arguments, index must be numerical"
    exit 
  }

  silent = 0
  _poslistmv($1, idx, silent)
}'


#%IU%(motor, index)
#%MDESC%
# Move the given motor at the position corresponding to the given index
# within a pre-configured positions list.
# The index can be float, then the target position will be interpolated
# inbetween the two nearest pre-configured positions.
# The function waits for the end of the motion.
# Returns non null if an error occured.
# 
def _poslistmv(num, idx, silent) '{
  global ICEPAP[]
  local  mne
  local  dev
  local  addr
  local  cmd
  local  ans
  local  ret



  # No verification on parameters, must have been done in the calling macro
  mne  = motor_mne(num)
  dev  = ICEPAP[mne]["dev"]
  addr = ICEPAP[mne]["addr"]

  # Launch the motion
  cmd = sprintf("MOVEL %f", idx)
  ans = _icepap_query(dev, "#" addr, cmd)
  if(index(ans, "ERROR")) {
    print ans
    return(-1)
  }


  # Handle the <ctrl-c>
  cdef("cleanup_once", sprintf("_poslist_stop(\"%s\")\n", mne), "_poslist_icepap")

  # Cosmetics
  if(!silent) {
    printf("\n")
  }

  # Wait for the end of the motion
  for(ret=0x02;ret;sleep(UPDATE)) {
    # Display motor position in user units
    if(!silent) {
      printf("%f\r",icepap_cmd(num, "position"))
    }
    ret  = icepap_cmd(num, "get_status")
  }

  # Cosmetics
  if(!silent) {
    printf("\n")
  }

  # Position discrepancies between spec and the
  # motor hardware will be silently resolved in favor of the hardware
  read_motors(0x06)

  # Normal end
  return(0)
}'


#%IU%(motor)
#%MDESC%
# Stop the motion on the speficied motor 
# and resynchronize silently SPEC position
#
def _poslist_stop(mne, silent) '{
  local num
  
  if((num=motor_num(mne)) == -1) {
    if(!silent) {
      _icepap_err
      printf("major internal error on _poslist_stop(\"%s\")\n", mne)
    }
    return(-1)
  }
  
  if(!silent) {
    printf("\nICEPAP WARNING: Aborting motion on \"%s\"\n", mne)
  }
  icepap_cmd(num, "abort_one")

  # Position discrepancies between spec and the
  # motor hardware will be silently resolved in favor of the hardware
  read_motors(0x06)

  return(0)
}'


#%UU% motor source_signal [start_index]
#%MDESC%
# The given motor will track the given source signal and move 
# within the previously loaded list of positions starting from
# the position corresponding to the optional given index in
# the list.
# 
def poslisttrackon '{
  local  src_sig
  local  dev
  local  addr
  local  cmd
  local  ans
  local  tt[]
  local  npos

  # Minium check on arguments
  if($#<2) {
    _icepap_err
    print "Missing arguments"
    print "Usage : $0 motor source_signal start_index"
    print "Signal: " _ICELIST_LTRACKSRC
    exit
  }

  # Minimum check on motor argument 
  mne = "$1"
  if(_icepap_ismot(mne)) { 
    _icepap_err
    print "Not an IcePAP motor"
    exit 
  }

  # Retrieve access to the IcePAP axis
  dev  = ICEPAP[mne]["dev"]
  addr = ICEPAP[mne]["addr"]


  # Minimum check on source argument 
  src_sig = icepap__toupper("$2")
  if(!index(_ICELIST_LTRACKSRC, src_sig)) {
    _icepap_err
    print "Wrong source signal, must be one of the following:"
    print "\""_ICELIST_LTRACKSRC"\""
    exit
  }

  # Check that a position list has been already loaded
  cmd = sprintf("?LISTDAT")
  ans = _icepap_query(dev, addr, cmd)
  if(index(ans, "ERROR")) {
    return
  }
  split(ans, tt)
  if(sscanf(tt[0], "%d", npos) != 1) {
    _icepap_err
    print "Invalid answer from DSP, giving up"
    return
  }
  if(!npos) {
    _icepap_err
    print "No position list loaded for motor \"" mne "\""
    print "Hint: use macro \"poslistsetup\""
    exit
  }

  if($# == 3) {
    _poslisttrackon($1, src_sig, $3)
  } else {
    _poslisttrackon($1, src_sig)
  }
}'


#%UU% motor
#%MDESC%
# The given motor will no more track a signal.
# Its position is silently updated.
# 
def poslisttrackoff '{
  local  mne

  # Minium check on arguments
  if($#!=1) {
    _icepap_err
    print "Missing arguments"
    print "Usage : $0 motor"
    exit
  }

  # Minimum check on motor argument 
  mne = "$1"
  if(_icepap_ismot(mne)) { 
    _icepap_err
    print "Not an IcePAP motor"
    exit 
  }

  _poslisttrackoff($1)

}'


#%IU%(motor, src_sig) or (motor, src_sig, idx)
#%MDESC%
# The given motor will track the given source signal and move 
# within the previously loaded list of positions starting from
# the position corresponding to the optional given index in
# the list.
# Returns non null if an error occured.
# 
def _poslisttrackon(num, src_sig, idx, silent) '{
  global ICEPAP[]
  local  cmd
  local  mne
  local  dev
  local  addr
  local  use_idx

  # No verification on parameters, must have been done in the calling macro
  mne  = motor_mne(num)
  dev  = ICEPAP[mne]["dev"]
  addr = ICEPAP[mne]["addr"]

  # optional index argin
  use_idx  = (whatis("idx") & 0x08000000)?0:1

  #
  if(use_idx) {
    cmd = sprintf("POS %s %d", src_sig, idx)
    ans = _icepap_query(dev, "#" addr, cmd, silent)
    if(index(ans, "ERROR")) {
      return
    }
  }

  # 
  # NOTE MP 14 Mar 2017: the TRIGGER option can not be given with
  # a source signal. Therefore we can handle it as another source.
  # By the way, the name TRIGGER is not very explicit, AUTO would
  # be better as the motor will automatically move from one position
  # in the list to another one.
  cmd = sprintf("LTRACK %s", src_sig)
  ans = _icepap_query(dev, "#" addr, cmd, silent)
  if(index(ans, "ERROR")) {
    return
  }
}'


#%IU%(motor)
#%MDESC%
# The given motor will no more track a signal.
#
def _poslisttrackoff(num, silent) '{
  local mne

  mne  = motor_mne(num)
  _poslist_stop(mne, silent)
}'



#
# ----------------------- binary transfers toolkit -------------------------
#


#%IU%(dev, addr, cmd, array, silent)
#%MDESC%
# Send the binary command with its data given in a SPEC data array.
# Supported types for the array are: 
#   short   (16bits)
#   ushort  (16bits)
#   ulong   (32bits)
#   float   (32bits)
#   double  (64bits)
#
# Example: 
#   ushort array shdata[4]
#   array_op("fill",shdata,2,2)
#   _icepap_send_array("isgtmp5","#0","*ISG BINDATA WORD 4",shdata)
#
def _icepap_send_array(dev, addr, cmd, data, silent) '{
  local sl
  local datas datal



  sl = (dev + 0 == dev)? 1 : 0
  if((index(dev,":") == 0) && !sl) { dev=sprintf("%s:5000",dev) }


  # check argins validity
  if (substr(cmd, 1, 1) != "*") {
    _icepap_err
    print "Bad binary command format"
    return
  } else if (whatis("data") & 0x0001ffff != 0x00010004) {
    _icepap_err
    print "Bad binary data array type"
    return
  }


  # guess the data size in words
  if((datas = _icepap_array_size(data)) == -1) {
    return
  }

  # if no conversion is needed, then send the data
  if(datas == 1) {
    return _icepap_send_ushortarray(dev, addr, cmd, data, silent)
  }

  # convert typed data to binary word array
  datal = array_op("rows", data) * array_op("cols", data) * datas
  local ushort array dataw[datal]
  array_copy(dataw, data)
  return _icepap_send_ushortarray(dev, addr, cmd, dataw, silent)
}'




#%IU%(dev, addr, cmd, array, silent)
#%MDESC%
# Send the binary command with its data given in a SPEC ushort data array.
#
def _icepap_send_ushortarray(dev, addr, cmd, dataw, silent) '{
  local i
  local sl
  local ack
  local datas
  local ulong array datal[3]
  local ret



  sl = (dev + 0 == dev)? 1 : 0
  if((index(dev,":") == 0) && !sl) { dev=sprintf("%s:5000",dev) }


  # check argins validity
  if(!index(whatis("dataw", "info"), "ushort")) { 
    _icepap_err
    print "Bad binary data array type, must be \"ushort\""
    return
  }


  # prepare binary protocol header
  datal[0] = 0xa5aa555a

  # preprare data length in words
  datas    = 1
  datal[1] = array_op("rows", dataw) * array_op("cols", dataw) * datas

  # the checksum has to be calculated over words
  datal[2] = (array_op("sum", dataw)) & 0xffffffff
  #for(i=0, datal[2]=0;i<datal[1]/datas;i++){
  #  datal[2] += data[i] & 0xffff
  #  if(datas>1) {
  #    datal[2] += (data[i]>>16) & 0xffff
  #  }
  #}

  # make sure acknowledge is properly treated
  if(substr(addr, 1, 1) == "#")
    ack = 1

  if(!silent) {
    printf("\tCommand       : \"%s\"\n", cmd)
    printf("\tData size     : %d word%s\n", datas,    datas>1?"s":"")
    printf("\tData length   : %d word%s\n", datal[1], datal[1]>1?"s":"")
    printf("\tCheck sum     : 0x%08X\n",    datal[2])
    printf("\tAcknowledge   : %s\n",        ack?"YES":"NO")
  } 


  # send command line
  _icepap_wr(dev, addr, cmd)

  # send header
  # 2 words startup mark (ex: 0xa5aa555a)
  # 2 words for the binary data length
  # 2 words for the checksum
  sock_put(dev, datal, 3)

  # send data block
  sock_put(dev, dataw)

  # wait for acknowledge
  if(ack) {
    ret = sock_get(dev, "\n")
    return(ret)
  } else
    return
}'






#%IU%(array)
#%MDESC%
# Returns the size in bytes of the data type of the given array.
# Otherwise -1 if not supported array type
#
def _icepap_array_size(dataw) '{

   # guess the data size in words
   if(index(whatis("dataw", "info"), "ushort")) { return(1) }
   if(index(whatis("dataw", "info"), "short"))  { return(1) }
   if(index(whatis("dataw", "info"), "ulong"))  { return(2) }
   if(index(whatis("dataw", "info"), "long"))   { return(2) }
   if(index(whatis("dataw", "info"), "float"))  { return(2) }
   if(index(whatis("dataw", "info"), "double")) { return(4) }

   _icepap_err
   print "Non supported type for data array"
   return(-1)

}'


#%IU%(array)
#%MDESC%
# Returns a string corresponding to IcePAP data type
# Otherwise "ERROR" if not supported array type
#
def _icepap_array_type(dataw) '{

   # guess the data size in words
   if(index(whatis("dataw", "info"), "ushort")) { return("WORD")   }
   if(index(whatis("dataw", "info"), "short"))  { return("WORD")   }
   if(index(whatis("dataw", "info"), "ulong"))  { return("DWORD")  }
   if(index(whatis("dataw", "info"), "long"))   { return("DWORD")  }
   if(index(whatis("dataw", "info"), "float"))  { return("FLOAT")  }
   if(index(whatis("dataw", "info"), "double")) { return("DFLOAT") }

   _icepap_err
   print "Non supported type for data array"
   return("ERROR")

}'


#%IU%(dev,addr,cmd,array)
#%MDESC%
# NOT IMPLEMENTED YET
#
def _icepap_get_ushortarray(dev, addr, cmd, dataw) '{
  local sl
  local nwords
  local bin_chksum bin_chksum_rd
  local ulong array datal[1]
  local ret

  sl = (dev + 0 == dev)? 1 : 0

  if((index(dev,":") == 0) && !sl) { dev=sprintf("%s:5000",dev) }


  # check argins validity
  if(substr(cmd, 1, 2) != "?*") {
    _icepap_err
    print "Bad binary request command format"
    return
  } else if(!index(whatis("dataw", "info"), "ushort")) { 
    _icepap_err
    print "Bad binary data array type, must be \"ushort\""
    return
  } 



  # send command line and get its ASCII answer
  printf("\tCommand       : %s\n", cmd)
  _icepap_wr(dev, addr, cmd)
  sock_par(dev,"timeout",0.5)
  ret = sock_get(dev,"\n")
  if(ret=="") {
    _icepap_err
    print "ERROR missing acknowledge"
    return
  }

  ret = substr(ret,0,length(ret)-1)
  printf("\tAnswer        : %s\n", ret)
  if(index(ret,"ERROR")) {
    return
  }
   

  # 2 words startup mark (ex: 0xa5aa555a)
  sock_get(dev, datal)

  # 2 words for the binary data length
  sock_get(dev, datal)
  nwords = datal[0]
  printf("\tData words    : %d\n", nwords)

  # resize the data array given as argin
  ushort array dataw[nwords]

  # 2 words for the checksum
  sock_get(dev, datal)
  bin_chksum_rd = datal[0]
  printf("\tCheck sum     : 0x%08X\n", bin_chksum_rd)

  # data block
  sock_par(dev,"timeout",2)
  sock_get(dev, dataw)

  # check data validity
  bin_chksum = (array_op("sum", dataw)) & 0xffffffff
  printf("\tData validity : %s\n", (bin_chksum==bin_chksum_rd)?"OK":"ERROR")


  return
}'



#%MACROS%
#%IMACROS%
#%AUTHOR% MP BLISS (Original 9/07).
# %BR%$Revision: 1.87 $ / $Date: 2017/09/25 13:32:14 $
#%TOC%