esrf

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

#%TITLE% ID_tango.mac
#$Revision: 1.27 $
#%NAME% Macros for a the use of the (in 2012) new Insertion Device tango
# server.
#%DESCRIPTION%
# The macros provide users with an interface between SPEC and the (in 2011) new
# Insertion Device tango server.
#%INTERNALS%
# One server, which handles an entire beam line, is handled by one macro
# hardware controller. Underneath, one will
# find the "MovableNames" (ie.
# U32u_GAP,  U32u_TAPER, HU52m_GAPBX, HU52m_GAPBZ, HU52m_PHASE, HU44m_GAPBX,
# HU44m_GAPBZ, HU44m_PHASE) as channel.
#%SETUP%
#%BR%\0%BR%%B%Please\0note,\0that\0there\0is\0a\0python\0utility\0to\0adapt%B%
#%B%the\0config\0file\0to\0the\0use\0of\0a\0beam\0line\0undulator\0tango%B%
#%B%device\0server!%B%
#%BR% %BR% Put the macro %B%ID_makewid%B% into the setup to create the macro
#%B%wid%B%, which will allow you to only see the undulator motors.
#%BR% %BR%When doing the config by hand, declare a %B%motor\0controller%B%:
#%BR% %BR%
#%PRE%
#MOTORS\0\0\0\0DEVICE\0\0\0\0\0\0ADDR\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0<>MODE\0\0NUM\0\0\0\0\0\0\0\0<>TYPE%BR%
#\0\0\0YES\0\0\0\0__ID\0\0\0\0\0\0\0//asc:10000/id/master/id00\0\0\0\0\0\0\0\0\0\010\0\0Macro Motors
#%PRE%
#%BR% %BR%
#Then create %B%macro\0motors%B% in a similar manner:
#%BR% %BR%Unit is index number of the controller, the channel num is the index
# of the movable as returned by the call tango_io("???", "MovableNames", x);%BR%
#%BR% %BR%
#%PRE%
#Number:\0<>Controller\0\0\0\0\0\0\0\0\0\0\0x:\0MAC_MOT
#Unit/[Module/]Channel\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\00/1
#Name\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0U32u_GAP
#mnemonic\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0U32u
#Steps per degree/mm\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\01000
#Sign of user * dial\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\01
#Backlash [steps]\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\00
#Steady-state rate [Hz]\0\0\0\0\0\0\0\0\0\0\0\0\0\0\05000
#Base rate [Hz]\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\01
#Acceleration time [msec]\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\01
#Motor accumulator\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\00
#Restrictions <>\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0NONE
#%BR% %BR%
#Dial = accumulator / steps
#\0\0High limit\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0300.0000
#\0\0Current\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\00.0000
#\0\0Low limit\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\011.5000
#User = sign * dial + offset
#\0\0Offset\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\00.0000
#\0\0`High' limit\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0300.0000
#\0\0Current\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\00.0000
#\0\0`Low' limit\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\011.5000
#%PRE%
#%BR%
# Then hit 'm' twice. Set the following for your motor:
#%PRE%
#Hardware\0read\0mode\0<>\0\0\0\0\0\0\0\0%B%NO\0QUERY\0=NQ%B%
#%PRE%
# If you miss this, you will be asked to confirm motor positions after each
# change on a revolver.
#%END%
#%HISTORY%
#$Log: ID_tango.mac,v $
#Revision 1.27  2022/03/11 12:28:59  guilloud
#fix bug in config velocity: use steps per mm rather than fixed 1000.
#
#Revision 1.26  2021/03/15 16:45:49  guilloud
#change acs for acs.esrf.fr to avoid possible timeout
#
#Revision 1.25  2019/11/08 11:16:23  ohlsson
#another small bug
#
#Revision 1.24  2019/11/07 16:29:08  ohlsson
#*** empty log message ***
#
#Revision 1.23  2019/11/07 16:02:58  ohlsson
#fixed small bug
#
#Revision 1.22  2019/11/07 15:44:54  ohlsson
#*** empty log message ***
#
#Revision 1.21  2019/11/07 15:31:07  ohlsson
#made it ready for EBS
#orion -> asc and id/id/00 to id/master/id00
#
#Revision 1.20  2016/12/02 13:26:27  guilloud
#changed TANGO_ERR tests
#
#Revision 1.19  2013/05/29 07:38:27  witsch
#__ID_config although they had been defined in the new macro set.
#
#Added idbody and a variable for treatment with idbody.
#
#Revision 1.18  2013/03/12 12:11:26  lagier
#ID_switch_undulator command timeout changed from 60 to 120 sec.
#one revolver takes about 80 minutes to switch on ID20.
#
#Revision 1.17  2013/02/13 14:00:03  witsch
#On request of Peter Bösecke, write a new macro in file ID_tango.mac,
#which reads the undulator positions, without them being defined as motor.
#
#This is particularly useful, when using mistart (from machinfo) to save
#the current and other info and which historically also saved the ID positions.
#
#The new macro is called: idshow and it is linked to the machinfo.mac
#macro _miwrite (i.e.. def _miwrite 'mishow').
#
#Also I added idon and idoff, to allow users to just en/disable all ID motors.
#
#Revision 1.16  2013/02/05 13:02:12  witsch
#one bug, one improvement
#
#the bug: line 686, where the second arg of _ID_ustate was the undulator name rather than
#the index number. This would always yield the status of the first index and that, of
#course would be wrong in most cases :-(
#
#the improvement: line 644, where a check for the undulator state could be ALARM, which
#allows to continue, but the macros would block. Now the check is for NOT ON AND NOT
#ALARM.
#
#Revision 1.15  2012/10/31 16:45:23  witsch
#error messages have been completed with TANGO_ERR_STACK["0"]["desc"] on advice of J. Meyer.
#the check on completion of a gap movement is done via checking for state ON rather than
#moving on advice of C.Penel
#Created a macro ID_makewid, to allow the user to create the macro wid with
#the current motor names. To be added to setup.
#
#Revision 1.13  2012/10/19 08:45:54  witsch
#more corrections for the ID_switch_undulator macro and the enable_disable
#function.
#
#Revision 1.12  2012/10/18 12:05:22  witsch
#bug fix in motor_par disable during config.
#
#Revision 1.11  2012/10/16 13:18:46  witsch
#more bug fixing for revolver undulators.
#
#Revision 1.10  2012/10/12 13:39:15  witsch
#changes found during tests with the ID18 revolvers.
#
#Revision 1.9  2012/08/21 09:23:27  witsch
#as demanded by Jens in jira ticket IDCO-49, added :
#tango_io(dev, "source", value) with value=0 (source=DEVICE only) .
#
#some local variable clean up.
#
#Revision 1.8  2012/05/09 13:41:04  witsch
#revised the part where ds and spec velocity are synchronised.
#
#Revision 1.7  2012/05/03 12:04:26  witsch
#moved id_state() from ID_tango.mac to machinfo.mac
#
#Revision 1.6  2012/05/03 07:58:36  witsch
#added function id_state(), which is needed on some beam lines. used to be in
#id.mac.
#
#Revision 1.5  2012/03/26 11:24:26  witsch
#Berruyer added function __ID_ismoving
#
#Revision 1.4  2012/03/05 09:54:00  witsch
#Commented out the acceleration part of the __ID_par() function, as it is
#going to be imprecise, due to the storage of the acceleration time in spec
#as integer milliseconds.
#
#Revision 1.3  2012/03/01 10:01:56  witsch
#more testing done on real undulators on ID21 (helas without any revolvers)
#and also to make sure everything works after a fresh start.
#Documentation has been looked through.
#
#Revision 1.2  2011/12/21 15:40:20  witsch
#new version with thorough testing on test device id/id/00.
#
#Revision 1.1  2011/04/15 14:33:12  witsch
#Initial revision
#

need spec_utils

if (!(whatis("__id_debug")    & 2)) rdef __id_debug \'#$*\'
if (SPEC == "holg") {
    if (!(whatis("__id_verbose")  & 2)) rdef __id_verbose \'eprint "ID >>> "\'
} else {
    if (!(whatis("__id_verbose")  & 2)) rdef __id_verbose \'#$*\'
}

#%IU%
#%MDESC% toggle debug mode for the present macros.
def ID_debug '{
    if ((whatis("__id_debug") >> 16) <= 5) { # just a # sign -> off
        rdef __id_debug "eprint \"ID: \", "
        eprint "ID debug is now ON"
    } else {
        rdef __id_debug \'#$*\'
        eprint "ID debug is now OFF"
    }
}
'

#%IU%
#%MDESC% called after each tango_[get|io] call. Checks for errors.
def __id_tango_catch_error '{
    if (TANGO_ERR != "0") {
        tty_cntl("md")
        #__errstr = what "(\"" addr "\", \"" attr "\")"
        __errstr    =   __ID["macfname"] " - " func " - ERROR: " what \
            "(\"" addr "\", \"" attr "\"" add ")\n" \
            TANGO_ERR_STACK["0"]["desc"] "\n"
        __ID_error(__errstr)
        __id_debug  TANGO_ERR_STACK
        tty_cntl("me")
        return ".error."
    }
}
'

#%IU%(mnum, type, unit, mod, chan)
#%MDESC%
# The macro motor configuration function
def __ID_config(mnum, type, unit, mod, chan) '{
    # chan will be used for counters to designate the tag address. Leave alone.
    __id_debug "Configuring ID", mnum, type, unit, mod, chan
    # take the old macros out
    local oldmacs, aux[], mac, str
    local stepsmm
    oldmacs =   "id_config id_cmd idunsetup id_poll
                id_axedef id_getallinfo id_showinfo id_getallpos id_getpos
                id_moveone id_prompt id_premove id_moveall"
    split(oldmacs, aux)
    for (mac in aux) {
        if (whatis(aux[mac]) & 2) {
            str =   "undef " aux[mac]
            eval(str)
        }
    }
    # we still need this one, but empty.
    rdef idsetup \'#$*\'
    unglobal oldmacs, aux, mac, str

    local func, attr, what, __errstr, mov
    func    =   "__ID_config()"
    addr    =   __ID_ADDR
    what    =   "tango_get"
    if (type == "ctrl") {
        # delete all the associative arrays to do a clean job.
        unglobal __IdDev[],  __ID[],     __IdRevolvers[]
        unglobal __IdRevo[], __IdUndi[], __IdSrPos[]
        global __IdDev[],  __ID[],     __IdRevolvers[]
        global __IdRevo[], __IdUndi[], __IdSrPos[]

        local x # can`t unglobal and reconstitute right afterwards.
        __ID["savedir"]     =   SPECD "/../../../local/spec/userconf"
        __ID["macfname"]    =   "ID_tango.mac"
        local und, undnum, movnum, loop, Undi, mov, aux[]
        attr    =   "UndulatorRevolverCarriage"
        loop = tango_get(addr, attr, aux)
        __id_tango_catch_error

        for (und = 0; und < loop; und ++) {
            __IdRevo[und] = aux[und]
        }

        attr    =   "UndulatorSrPositions"
        loop = tango_get(addr, attr, aux)
        __id_tango_catch_error

        for (und = 0; und < loop; und ++) {
            __IdSrPos[und] = aux[und]
        }

        attr    =   "MovableNames"
        movnum = tango_get(addr, attr, aux)
        __id_tango_catch_error

        for (mov = 0; mov < movnum; mov ++) {
            __IdDev[mov] = aux[mov]
        }

        # Revolvers
        attr    =   "UndulatorNames"
        loop = tango_get(addr, attr, aux)
        __IdUndi["number"] = loop
        __id_tango_catch_error
        for (und = 0; und < loop; und ++) {
            __IdUndi[und] = aux[und]
            __IdUndi[aux[und]]   =   und
            if (__IdRevo[und]) { # isa revolv.
                local pos
                pos =   __IdSrPos[und]
                # stuff all unds on same position in here
                if (__IdRevolvers[pos]) {
                    __IdRevolvers[pos]    =   __IdRevolvers[pos] " " __IdUndi[und]
                } else {
                    __IdRevolvers[pos]    =   __IdUndi[und]
                }
            }
        }
        __ID["unit"] = __ID_ADDR # saving for the spec bug in _par
        if (whatis("__IdRevolvers") & 0x05000000) {
            __ID["norevolvers"] = 0
        } else {
            unglobal __IdRevolvers
            __ID["norevolvers"] = 1
        }
        # Add tango_io(dev, "source", value) with value=0 (source=DEVICE only).
        # Avoids reading from the device cache when  polling the
        # device. 
        attr    =   "source"
        __id_debug "tango_io(\"" addr "\", \"" attr "\", 0)"
        tango_io(addr, attr, 0)
        __id_tango_catch_error
    }
    else if (type == "mot") {
        __ID[chan]    =   __IdDev[chan]
        __ID["und"][chan] = substr(__IdDev[chan], 1, index(__IdDev[chan], "_")-1)
        local str, i, n, bla[], mne
        mne = motor_mne(mnum)
        __ID[__ID[chan]] = mne
        __ID["mot"][chan] = mne
        __ID["motors"] = __ID["motors"] " " mne
        motor_par(mnum, "backlash", 0)      # is handled by server
        # spec will not write the velocity, unless it is different from a stored value.
        # This will set the velocity in the tango DS to the value set in the
        # Spec config!!!!
        # Read the velocity from server and use motor_par to set it.
        local dsvelo
        addr    =   __ID["unit"]
        what    =   "tango_get"
        attr    =   __IdDev[chan] "_Velocity"
        __id_debug "tango_get(\"" addr "\", \"" attr "\")"
        TANGO_ERR = "-1"
        stepsmm = motor_par(mnum, "step_size")
        dsvelo  =   tango_get(addr, attr) * stepsmm
        # do we want to flood the user with error messages?
        #__id_tango_catch_error
        if (! TANGO_ERR ){
            __id_verbose mne, "dsvelo", dsvelo
            motor_par(mnum, "velocity", dsvelo) # set spec velocity to ds velocity
        }
        __id_verbose "Motor", motor_name(mnum), "(mne", mne ") handles", __ID[chan]
        delete __IdDev[chan]
        local undnum, state
        state =   __ID_mstate(__ID["unit"], chan)
        __id_debug "state of", mne, state
        if (state == "DISABLE") {
            motor_par(mnum, "disable", 1)  # disable
        } else {
            motor_par(mnum, "disable", 0)  # enable
        }
    }
}
'

#%IU%(mnum, cmd, p1, p2, p3)
#%MDESC%
# The macro motor command function
def __ID_cmd(mnum, cmd, p1, p2, p3) '{
    local unit, chan, retval, attr
    __id_debug "Command ID", mnum, cmd, p1, p2, p3, "(" __ID["unit"] ")"
    local func, attr, what, __errstr
    func    =   "__ID_cmd()"
    addr    =   __ID["unit"]
    if (!addr) { # when macro has just been loaded and a reconfig is performed
        addr    =   __ID_ADDR
    }
    what    =   "tango_io"
    if (mnum == "..") { # can`t remember, why I do this
        unit    =   p1
        if  (cmd == "preread_all") {
            __ID_state(addr)
            return
        }
    }
    else {
        unit = motor_par(mnum, "unit")
        chan = motor_par(mnum, "channel")
        if (cmd == "start_one") {
            local state
            attr = __ID[chan] "_Position"
            state   =   __ID_mstate(__ID["unit"], chan)
            __id_debug "*********", state, attr, p1
            if (state == "DISABLE") {
                local str
                str = "Undulator " __ID[__ID[chan]]"/" __ID[chan]  " disabled! (start_one)\n"
                __ID_error(str)
                return ".error."
            }
            __ID[attr]["started"]   =   1
            addr    =   __ID["unit"]
            what    =   "tango_put"
            __id_debug "tango_put(\"" addr "\", \"" attr "\", " p1 ")"
            tango_put(addr, attr, p1)
            __id_tango_catch_error
        }
        else if (cmd == "position") {
            local position, state
            state   =   __ID_mstate(__ID["unit"], chan)
            __id_debug "position:", chan, __ID[chan],  __ID[__ID[chan]], __ID[__ID[__ID[chan]]], "state is", state
            if (state == "DISABLE") {
                local str
                str = "Undulator " __ID[__ID[chan]]"/" __ID[chan]  " disabled (in position) !\n"
                __ID_error(str)
                return NaN #-1
            }
            what    =   "tango_get"
            attr = __ID[chan] "_Position"
            __id_debug "tango_get(\"" addr "\", \"" attr "\")"
            retval = tango_get(addr, attr)
            __id_tango_catch_error
            return retval
        }
        else if (cmd == "get_status") {
            local state, xtra[], x
            attr = __ID[chan] "_Position"
            state = __ID_state(addr)
            __id_debug "get_status:", chan, __ID[chan],  __ID[__ID[chan]], __ID[__ID[__ID[chan]]], "state is", state
            if (state == "MOVING") {
                return 2
            } else
            if (state == "ON") {    # all good
                return 0
            } else {                # anything else is bad
                return 0x20
            }
        }
        else if (cmd == "abort_one") {
            attr    =   "Abort"
            __id_debug "tango_io(\"" addr "\", \"" attr "\")"
            tango_io(addr, attr)
            __id_tango_catch_error
        }
    }
}
'

#%IU%(und)
#%MDESC%Get state of all undulators%BR%
#%END%
# Info from J. Meyer 12/12/11 on states%BR%
# Von ON = 0 bis UNKNOWN=13%BR%
# enum DevState { ON, OFF, CLOSE, OPEN, INSERT, EXTRACT, MOVING,%BR%
#                 STANDBY, FAULT, INIT, RUNNING, ALARM, DISABLE,%BR%
#                 UNKNOWN };
def __ID_state(addr) '{
    __id_debug "state from", addr
    local state, aux[], stenum[], str
    str     =   "ON OFF CLOSE OPEN INSERT EXTRACT MOVING STANDBY FAULT INIT
                 RUNNING ALARM DISABLE UNKNOWN"
    if (!addr) { # when macro has just been loaded and a reconfig is performed
        return -1
    }
    split(str, stenum)
    local func, attr, what, __errstr
    func    =   "__ID_state()"
    what    =   "tango_get"
    attr    =   "State"
    __id_debug "tango_get(\"" addr "\", \"" attr "\", aux)"
    state   =   tango_get(addr, attr)
    __id_tango_catch_error
    return stenum[state]
}
'


#%IU%(mov)
#%MDESC%Get state of one movable%BR%
#%END%
# Von ON = 0 bis UNKNOWN=13%BR%
# enum DevState { ON, OFF, CLOSE, OPEN, INSERT, EXTRACT, MOVING,%BR%
#                 STANDBY, FAULT, INIT, RUNNING, ALARM, DISABLE,%BR%
#                 UNKNOWN };
def __ID_mstate(addr, mov) '{
    __id_debug "state from", addr, mov
    local state, aux[], stenum[], str
    str     =   "ON OFF CLOSE OPEN INSERT EXTRACT MOVING STANDBY FAULT INIT
                 RUNNING ALARM DISABLE UNKNOWN"
    if (!addr) { # when macro has just been loaded and a reconfig is performed
        return "UNKNOWN"
    }
    split(str, stenum)
    local func, attr, what, __errstr
    func    =   "__ID_mstate()"
    what    =   "tango_get"
    attr    =   "MovableStates"
    # get states of all movables on the beam line (up to 6 if revolvers everywhere)
    __id_debug "tango_get(\"" addr "\", \"" attr "\", aux)"
    state   =   tango_get(addr, attr, aux)
    __id_tango_catch_error
    #__id_debug aux
    if (mov == -1) {    # be able to grab the entire ass array.
        local x, tmp[]  # for some reason I can`t return aux
        for (x in aux) {# nor can I copy it.
            tmp[x] = stenum[aux[x]]     # so, profit of this problem and return readable strings
        }
        return tmp      # but I can return a one by one copy

    } else {
        state = aux[mov]
        __id_debug "for movable", und, "return", stenum[state]
        return stenum[state]
    }
}
'



#%IU%(und)
#%MDESC%Get state of one undulator%BR%
#%END%
# Info from J. Meyer 12/12/11 on states%BR%
# Von ON = 0 bis UNKNOWN=13%BR%
# enum DevState { ON, OFF, CLOSE, OPEN, INSERT, EXTRACT, MOVING,%BR%
#                 STANDBY, FAULT, INIT, RUNNING, ALARM, DISABLE,%BR%
#                 UNKNOWN };
def __ID_ustate(addr, und) '{
    __id_debug "state from", addr, und
    local state, aux[], stenum[], str
    str     =   "ON OFF CLOSE OPEN INSERT EXTRACT MOVING STANDBY FAULT INIT
                 RUNNING ALARM DISABLE UNKNOWN"
    if (!addr) { # when macro has just been loaded and a reconfig is performed
        return "UNKNOWN"
    }
    split(str, stenum)
    local func, attr, what, __errstr
    func    =   "__ID_ustate()"
    what    =   "tango_get"
    attr    =   "UndulatorStates"
    # get states of all unds on the beam line (up to 6 if revolvers everywhere)
    __id_debug "tango_get(\"" addr "\", \"" attr "\", aux)"
    state   =   tango_get(addr, attr, aux)
    __id_tango_catch_error
    #__id_debug aux
    if (und == -1) {    # be able to grab the entire ass array.
        local x, tmp[]  # for some reason I can`t return aux
        for (x in aux) {# nor can I copy it.
            tmp[x] = stenum[aux[x]]     # so, profit of this problem and return readable strings
        }
        return tmp      # but I can return a one by one copy

    } else {
        state = aux[und]
        __id_debug "for undulator", und, "return", stenum[state]
        return stenum[state]
    }
}
'

#%IU%(und)
#%MDESC%return 1 if ondulator und_mne is moving 0 otherwise%BR%
#%END%
def __ID_ismoving(und_mne) '{
    local chan, state
    
    chan = motor_par(und_mne, "channel")
    state   =   __ID_mstate(__ID["unit"], chan)
    
    if (state != "ON") {
        return 1
    }
    
    return 0
 }
'


#%IU%(mnum, key, action, p1, p2)
#%MDESC%
# Called by spec with different keys to handle motor_par(mot, "key", new_value)
# actions.
#%BR%
#  %UL%
#  %LI%Velocity      - The velocity of a movable in mm/s %BR%
#       Spec: velocity  - steps per second.
#  %LI%FirstVelocity - The first velovity of the movable in mm/s%BR%
#       Spec: base_rate - steps per second.
#  %LI%Acceleration  - The movable acceleration in mm/s2%BR%
#       Spec: acceler.  - time in milliseconds for the motor to accelerate to
#       full speed
#  %XUL%
def __ID_par(mnum, key, action, p1, p2) '{
    # be very carful with the use of motor_par with other than the SPEC provided
    # key words, as __ID_ADDR gets lost at the end of this macro function.
    local unit, channel, stepsmm
    __id_debug "Parameters ID", mnum, key, action, p1, p2
    channel = motor_par(mnum, "channel")
    stepsmm = motor_par(mnum, "step_size")
    local addr, func, attr, what, __errstr, xtra[]
    func    =   "__ID_par()"
    addr    =   __ID["unit"]
#    if (key == "acceleration") {
#        attr = __ID[channel] "_Acceleration"
#        if (action == "set") {
#            what    =   "tango_get"
#            tango_get(addr, attr, xtra)
#            __id_tango_catch_error
#            if (("quality" in xtra) && (xtra["quality"] == 0)) { #quality valid!
#                what    =   "tango_put"
#                local base_rate, slew_rate
#                base_rate   =   motor_par(mnum, "base_rate")
#                slew_rate   =   motor_par(mnum, "slew_rate")
#                # this is going to be inherently imprecise, as the acceleration time is
#                # stored in integer milliseconds and the acceleration time will use fractions
#                # of milliseconds a lot. 
#                # Not sure, we should be dealing with that! So no action taken!!!!!
##                tango_put(__ID["unit"], attr, (slew_rate - base_rate) / p1)
##                __id_tango_catch_error
#            }
#            else {
#                return ".error."
#            }
#        }
#    } else
    if ((key == "velocity") || (key == "slew_rate")) {
        attr = __ID[channel] "_Velocity"
        if (action == "set") {
            what    =   "tango_get"
            tango_get(addr, attr, xtra)
            # do we want to flood the user with messages? 
            #__id_tango_catch_error
            if (("quality" in xtra) && (xtra["quality"] == 0)) { #quality valid!
                what    =   "tango_put"
                TANGO_ERR	= "-1"
                tango_put(__ID["unit"], attr, p1 / stepsmm)
                if (TANGO_ERR  == "API_AttrNotAllowed" ) {
                    local errstr
                    errstr = "Setting velocity for " motor_mne(mnum) " is not allowed!\n"
                    __ID_error(errstr)
                    return ".error."
                } else {
                    __id_tango_catch_error
                }
            }
            else {
                return ".error."
            }
        }
    }
    else if (key == "base_rate") {
        attr = __ID[channel] "_FirstVelocity"
        if (action == "set") {
            what    =   "tango_get"
            tango_get(addr, attr, xtra)
            __id_tango_catch_error
            if (("quality" in xtra) && (xtra["quality"] == 0)) { #quality valid!
                what    =   "tango_put"
                tango_put(__ID["unit"], attr, p1 / stepsmm)
                __id_tango_catch_error
            }
            else {
                return ".error."
            }
        }
    }
    else if (key == "isRevolver") {
        # this keyword can only be used, while the motor is enabled !!!!!!!!
        if (action == "get") {
            return __IdRevo[__IdUndi[__ID["und"][channel]]]
        }
    }
  

}
'

#%UU% undulator_name [keep-enabled]
#%MDESC%
# Switch undulator within a revolver setup. Without arguments, the macro will
# display the possible choices.%BR%
# If the second argument is present, all undulator motors will enabled,
# otherwise, only the motors on enabled undulators will be enabled.
#%END%
# this was specified by G. Berruyer in Feb. 2012! but I felt like there should
# be a choice for the user.
def ID_switch_undulator '{
    if (!(whatis("__ID") & 0x01000000)) {
        eprint "There seem to be no insertion devices in use."
        eprint "If you just did a fresh start, you might have to reconfig !"
        exit
    }
    local x
    # any revolvers at all, no, if __IdRevolvers is Unset
    if (__ID["norevolvers"]) {
        eprint "There seem to be no revolvers in your insertion devices!"
        exit
    }
    if (($# < 1) || ($# > 2)) {
        eprint "Usage:  ID_switch_undulator undulatorname [keep-enabled]"
        for (x in __IdRevolvers) {
            print "\tIn Position", x, "there are", __IdRevolvers[x]
        }
        print "Please use one of those names"
        exit
    }
    local und, undnum, kenab, state
    und = "$1"; undnum = __IdUndi[und]; kenab   =   "$2" ? 1: 0
    local addr, func, attr, what, __errstr
    func    =   "ID_switch_undulator"
    addr    =   __ID["unit"]
    if ( ! __ID["TeStInG"] ) { # we do need some testing!
        if ((__ID_state(addr) != "ON") && (__ID_state(addr) != "ALARM")) {
            eprint "The revolver is not in a state, where it can be turned!"
            exit
        }
    }
    __id_debug "ID_switch_undulator", und, undnum
    # check if that exists at all
    for (x in __IdUndi) {
        if (__IdUndi[x] == und) {
            break
        }
    }
    __id_debug x, __IdUndi["number"]
    if (x == __IdUndi["number"]) {
        eprint "There seems to be no undulator named", und, "available."
        for (x in __IdRevolvers) {
            print "\tIn Position", x, "there are", __IdRevolvers[x]
        }
        print "Please use one of those names"
        exit
    }
    state   =   __ID_ustate(__ID["unit"], undnum)
    __id_debug "state of undulator", und, undnum, state
    if (state != "DISABLE" ) {
        eprint "Undulator", und, "isn`t disabled. Thus you can`t enable it!"
        for (x in __IdRevolvers) {
            print "\tIn Position", x, "there are", __IdRevolvers[x]
        }
        print "Please use one of those names"
        exit
    } else {
        cdef("cleanup_once", "print \"Please do not abort this macro\"", "ID_switch_undulator")
        attr    =   "Enable"
        what    =   "tango_io"
        add     =   ", \"" und "\"" #
        __id_debug " ----------------- > switching", attr, und
        tango_io(addr, attr, und)
        __id_tango_catch_error
        add     =  ""
        local now, then, state
        now     =   time()
        while (1) {
            state   =   __ID_ustate(__ID["unit"], undnum)
            __id_tango_catch_error
            if ((state != "MOVING") && (state != "DISABLE")) {
                break
            }
            then    =   time() - now
            if (then > 120) { # just in case !
                __ID_error("Aborting after 120 seconds")
                break
            }
            printf("ID: Moving carriage %#2d\r", then)
        }
        __ID_enable_disable_motors(kenab)
        sleep(1) # temp solution to avoid getting position read errors :-(
        sync    # if Hardware read mode is not set to NQ, user will be asked
                # multiple times for position discrepancy confirmation!
    }
}
'


#%IU%(und)
#%MDESC% enables motors on enabled undulators and disables motors on disabled
# undulators.
def __ID_enable_disable_motors(kenab) '{
    # disable the motors associated with the disabled undulator, if wished.
    local mnum, aux[], channel, states[]
    split(__IdRevolvers[pos], aux)
    # get the states at once, to avoid multiple tango_gets
    states  =   __ID_mstate(__ID["unit"], -1)
    __id_debug states
    for (mnum = 0; mnum < MOTORS; mnum++) {
        if (motor_par(mnum, "device_id") != "__ID") {
            continue    # not an ID motor
        }
        channel = motor_par(mnum, "channel")
        __id_debug "motor", motor_mne(mnum), "channel", channel, "state", states[channel]
        motor_par(mnum, "disable", 0)
        
        if (!motor_par(mnum, "isRevolver")) {
            __id_debug motor_mne(mnum), " not a revo"
            continue    # not a revolver
        }
        if ((states[channel] != "DISABLE") || (kenab)) {
            ; # print "enable  motor", mnum, motor_mne(mnum)
        } else {
            #print "disable motor", mnum, motor_mne(mnum)
            motor_par(mnum, "disable", 1)
        }
    }
}
'


#%IU%(mesg)
#%MDESC% Writes indentical error message only once in a while.
def __ID_error(errstr) '{
    global __IDERR[]
    local expiry, now, str, x
    str = "ID: " errstr
    expiry  =   5   # seconds before message is redisplayed
    now     = time()
    if (errstr in __IDERR) {
        if ((time() - __IDERR[errstr]) > expiry) {
            __IDERR[errstr] = now
            cprint(str, 3, 1, 1)
        }
    } else {
        __IDERR[errstr] = now
        cprint(str, 3, 1, 1)
    }
    for (x in __IDERR) {
        if ((time() - __IDERR[x]) > 20) {
            delete __IDERR[x]
        }
    }
}
'


#%IU%()
#%MDESC% Returns "YES" for a Insertion Devices TANGO server, otherwise "NO".
def __ID_isatango() '{
    if (!(whatis("__ID") & 0x05000004) ) {
        __ID_error("No controller defined yet. Please call your BCU contact!")
        return ".error."
    }
    # taco_io allows an error message free call, when ESRF_ERR is preset to -1
    # tango_io doesn`t do that :-(
    # Update 4 Feb 2012: Gerry is going to allow for a TANGO_ERR = "-1" to do
    # the job! 
    TANGO_ERR="-1"
    if (__ID_state(__ID["unit"]) == -1) {
        return "NO"
    } else   {
        return "YES"
    }
}
'


#%UU%
#%MDESC% Print some information about the Insertion Devices to the screen.
#%BR%If you deem necessary to issue more information from this macro, please
#contact the author.
def ID_show '{
    local fmt, und, ustates[]

    ustates = __ID_ustate(__ID["unit"], -1)

    fmt     =   "%10s | %10s | %8s | %8s\n"
    # header
    print "Insertion devices information for", SPECBL
    printf(fmt, "Undulator",  "Revolver",   "Slot", "State")
    printf(fmt, "----------", "----------", "--------", "--------")

    for (und = 0; und < __IdUndi["number"]; und ++) {
        __id_debug und, __IdUndi[und]
        printf(fmt, __IdUndi[und], __IdRevo[und] ? "yes" : "no", __IdSrPos[und], \
               ustates[und])
    }
}
'


#%UU%
#%MDESC% Declares a new macro called "wid" to display ID positions.
def ID_makewid '{
    if (__ID["motors"]) {
        eval (sprintf("newwh wid %s", __ID["motors"]))
    }
}
'

#%UU%
#%MDESC%Disable all ID motors.%BR%
def idoff '{
    local mne
    for(mne=0; mne < MOTORS; mne++) {
        if (motor_par(mne, "device_id") == "__ID") {
            motor_par(mne, "disable", 1)
        }
    }
    __ID["ON"] = 0
}
'

#%UU%
#%MDESC%Enable all ID motors.%BR%
def idon '{
    local mne
    for(mne=0; mne < MOTORS; mne++) {
        if (motor_par(mne, "device_id") == "__ID") {
            motor_par(mne, "disable", 0)
        }
    }
    if (whatis("blmenu") & 0x2){
        blmenuadd("Insertion Device motors","ID info","idbody","_idblmenu_")
    }
    __ID["ON"] = 1
}
'

#%IU% use with blmenu
#%MDESC%
def idbody() '{
     if (__ID["ON"]) {
         idoff
     } else {
         idon
     }
     return(sprintf("%s",__ID["ON"]?"On":"Off"))
}
'


#%UU%
#%MDESC%Standalone ID position read routine. In case of connection problems
# with the server, the macro will remain silent
def idshow '{
    local type, aux[], machinehost, bl, hasid, addr
    local MovableNames[], MovableStates[], pos
    local func, attr, what, __errstr, i
    func        =   "idshow"
    what        =   "tango_get"
    machinehost =   "acs.esrf.fr:10000"
    bl          =    toupper(SPECBL)
    split(bl, aux, "D") # SPECBL is consistently Dxx or IDyy
    type        =   aux[0] == "I" ? "ID" : "D"
    if (type == "ID") {
        local   standalone
        addr    = sprintf("//%s/id/master/id%d", machinehost, aux[1])
        attr    =   "MovableNames"
        TANGO_ERR   =   "-1"
        movnum = tango_get(addr, attr, MovableNames)
        standalone  =   (whatis("titles")  & 0x40000000)
        if (TANGO_ERR != "0") {
            titles  =   "No connection to the Insertion device server"
            values  =   addr
        } else {
            attr    =   "MovableStates"
            TANGO_ERR   =   "-1"
            tango_get(addr, attr, MovableStates)
            for (i = 0; i < movnum; i ++) {
                #print MovableStates[i], MovableNames[i]
                FMT =   "%s %9.4f"
                if (!MovableStates[i]) { # State is ON
                    local mslen
                    if ((mslen = length(MovableNames[i])) > 9) {
                        FMT =   "%s %" mslen ".4f"
                    }
                    titles  =   sprintf("%s %9s", titles, MovableNames[i])
#                    TANGO_ERR   =   "-1"
                    attr    =   MovableNames[i]  "_Position"
                    pos     =   tango_get(addr, attr)
                    values  =   sprintf(FMT, values, pos)
                }
            }
        }
        if (!standalone) {  # print, if not called from mi_Fheader
            print titles
            print values
        }
    }
}
'

if (whatis("_miwrite") == 2) { # empty macro _miwrite
    rdef _miwrite \'idshow\'
}


#%MACROS%
#%IMACROS%
#%AUTHOR% H. Witsch, BLISS - ESRF
#$Revision: 1.27 $, $Date: 2022/03/11 12:28:59 $
#%TOC%