esrf

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

"""
#%TITLE%pace6000.mac
#$Revision: 1.6 $
#%NAME%
#Macromotor to control the PACE6000 K0443 Pressure Automated Calibration Equipment.

#%DESCRIPTION%
#Control of the PACE5000 single-channel or PACE6000 single/dual-channel Pressure Automated
#Calibration Equipment.
#%BR%Control via ethernet sockets.
#%BR%The macromotor controller to be used is 'pace6000'. Each controller
#must define the serial device index or the "adress:socket_number" as the address in config.

#Declare a %B%motor\0controller%B%:
#
#%PRE%
#MOTORS     DEVICE               ADDR  <>MODE  NUM         <>TYPE
#   YES   pace6000  id09pace6000:5025            2   Macro Motors
#%PRE%
# and a macro motor:
#%BR% %BR%Unit is index num of controller, second is the %B%controller\0channel%B%
#as number 0 (zero) or 1 (one).
#%PRE%
#Number: <>Controller          xx: MAC_MOT
#Unit/[Module/]Channel                 y/0
#Name                                 pace
#Mnemonic                             pace
#Steps per degree/mm                 10000
#Sign of user * dial                     1
#Backlash [steps]                        0
#Steady-state rate [Hz]               2000
#Base rate [Hz]                        200
#Acceleration time [msec]              125
#Motor accumulator                     362
#Restrictions <>                      NONE

#Dial = accumulator / steps
#  High limit                     xxx.0000
#  Current                          0.0000
#  Low limit                        0.0100
#User = sign * dial + offset
#  Offset                           0.0000
#  `High' limit                   xxx.0000
#  Current                          0.0000
#  `Low' limit                      0.0100
#%PRE%
#%BR%
# Then hit 'm' twice. Set the following for your motor:
#%PRE%
#Hardware read mode <>        PR + AL + NQ
#%PRE%
#Please NOTE: When the controller is close to zero, it mightn`t return the
#second in-limit parameter as 1. This implies, that the pressure zero will
#not be reached and the controller will never signal it has reached the final
#destination. Solution is to set the lower limit to %B%0.1%B%.
#%END%
$Log: pace6000.mac,v $
Revision 1.6  2015/06/02 13:12:56  witsch
* macro pace6000_slewrate corrected to work with the controller's
  pressure unit, but converted to units per minute. 
* bug fix, macro pace6000_slewrate : one motor_par used a bad variable
  as motor number.
* define macro pace6000_slewrate as pace_slewrate and pace6000_switch as 
  pace_switch to allow for pace5000 users not to be confused.

Revision 1.5  2015/05/29 15:21:47  witsch
* keep ping from talking.
* clarify message after ping failed
* check for controller state, before checking for motor state.
  if output:state? is 0, return 0 and motor is then not moving.

Revision 1.4  2014/03/27 15:09:22  witsch
macro pace6000_switch was working on only first channel, as the channel
number had been omitted with the first documentation.
Added the treatment of channel for :OUTPUT[x]:STATE command.

Revision 1.3  2014/02/25 08:29:56  witsch
bug in pace6000_switch has been fixed.

some safety in pace6000_io and pace6000_switch and pace6000_slew to make sure,
they're not used with wrong motors.

Revision 1.2  2014/02/18 09:52:09  witsch
Added macro to change the slewrate.

"""

global __PACE6000

#%UU%
#%MDESC%toggle the debug mode
if (!(whatis("_pace6000_debug")  & 2)) rdef _pace6000_debug \'#$*\'
def pace6000_debug '{
    if ((whatis("_pace6000_debug")>>16) <= 3) { # macro length is 3 bytes: comment
        rdef _pace6000_debug "eprint \">>> PACE6000 debug: \""
        printf ("_pace6000 macro debug mode is ON\n")
    } else {
        rdef _pace6000_debug \'#\$*\'
        printf ("_pace6000 macro debug mode is OFF\n")
    }
}
'



# some programmer help
#%UU%
#%MDESC%toggle simulation mode for the present macros.
def pace6000_simul '{
    if (__PACE6000["SIMUL"]) { # if present should be true
        __PACE6000["SIMUL"] = 0
        printf ("pace6000 simulation is OFF\n")
    } else {
        __PACE6000["SIMUL"]      = 1
        printf ("pace6000 simulation is ON\n")
    }
}
'


#%IU%
#%MDESC%MACRO MOTOR:
# Called by spec after reading the config file
def pace6000_config(mne, type, unit, num_motors) '{
    local  dev
    _pace6000_debug "config: ", mne, type, unit, num_motors
    # called for each macro-hardware controller
    # p1:unit p2: nbchane
    if(type == "ctrl") {
        local lcmd, dd, output
        if (split(pace6000_ADDR, dd,":")) { # ethernet
            lcmd = sprintf ("ping %s -c 3 >/dev/null 2>&1", dd[0])
            if (unix(lcmd, output) != 0) {
                eprintf ("PACE6000: Controller %s not responding, disabling controller.\n", dd[0])
                return(".error.")
            }
            __PACE6000[unit]["type"]  = "ethernet"
        } else { # serial
            __PACE6000[unit]["type"]  = "serial"
        }
        __PACE6000[unit]["dev"]   = pace6000_ADDR
        _pace6000_debug "       new controller ", _pace6000_io(unit, "*IDN?")
    }
    """
    else if (type == "mot") {
        This didn`t work, it is precisely like described in the manual (4-46)
        local chan, cmd
        chan = motor_par(mne, "channel")
        cmd = ":SENS" chan ":PRES:INL:time 1"
        _pace6000_io(unit, cmd)
    }
    """
}
'


"""
#%IU%
#%MDESC%
# MACRO MOTOR:
# Called by spec on motor operation. It manages:
#%BR%- position
#%BR%- start_one
#%BR%- get_status
"""
def pace6000_cmd(num,key,p1,p2) '{
    _pace6000_debug "_cmd:    ", num, key, p1, p2
    local unit, chan, cmd, aux

    if(num == "..") { return } # do nothing with controller

    unit = motor_par(num, "unit")
    chan = motor_par(num, "channel")
    # controller uses 1 or 2, if empty, communicate empty string for channel
    # so that would "" for channel 1 and 2 for channel 2 (for pace5000)
    if (chan) chan += 1
    else chan = ""

    if (key == "start_one") {
        # start a motion. p1: dial abs. pos., p2: dial rel. pos.
        local currstate
        cmd = ":OUTPut" chan ":STATe?"
        currstate = _pace6000_io(unit, cmd) * 1
        if (!currstate) {
            eprint "PACE6000: Controller not on. Use macro pace6000_switch!"
            return ".error."
        }
        cmd = ":SOURce" chan ":PRESsure " p1
        _pace6000_io(unit, cmd)
        if (_pace6000_report_error(unit) == ".error") {
            _pace6000_debug "_cmd:     ---------------  reporting an .ERROR."
            return ".error."
        }
        #returns nothing
    }
    else     if (key == "position") {
        # return the current pressure
        cmd = ":SENSe" chan ":PRESsure?"
        _pace6000_debug "_cmd:     position", cmd
        retval = _pace6000_io(unit, cmd) * 1
        if (_pace6000_report_error(unit) == ".error") {
            _pace6000_debug "_cmd:     ---------------  reporting an .ERROR."
            return ".error."
        }
        else {
            _pace6000_debug retval, "bar"
            return retval
        }
    }
    else if (key == "get_status") {
        """
        return motor status

        0x02: moving
        0   : otherwise

        Function: Query in-limits value
        Response: First parameter - current pressure.
        Second parameter - in limit:
                                        0 = not in limits
                                        1 = in limits
            NOTE: When the controller is close to zero, it mightn`t give the second parameter as 1.
        """
        local pressevent, aux[], pressure, currstate
        cmd = ":OUTPut" chan ":STATe?"
        currstate = _pace6000_io(unit, cmd) * 1
        if (! currstate) {
            return 0
        }

        cmd = ":SENS" chan ":PRES:INL?"
        pressevent = _pace6000_io(unit, cmd)
        if (_pace6000_report_error(unit) == ".error") {
            _pace6000_debug "_cmd:     ---------------  reporting an .ERROR."
            return ".error."
        }

        nb = split(pressevent, aux, ",")
        pressevent = aux[1] * 1

        if (pressevent) {
            return 0 # and return zero to signal, we`re there!
        }
        else {
            return 0x02
        }
    }
    else if (key == "abort_one") {
        """
        stop channel that is changing pressure
        There seems to be no ABORT command in the controller. Read the pressure and send it
        as set point instead.
        """
        local pressure
        # get the current pressure
        cmd = ":SENSe" chan ":PRESsure?"
        _pace6000_debug "_cmd: --------------------> abort_one", cmd
        pressure = _pace6000_io(unit, cmd) * 1
        cmd = ":SOURce" chan ":PRESsure " pressure
        _pace6000_debug "_cmd: --------------------> abort_one", cmd
        _pace6000_io(unit, cmd)
    }
}'


#%IU%
#%MDESC%sends the string 'cmd' to controller.
#%BR%It ends the command with \\r and \\n for serial comm.
#%BR%There could be a GPIB connection, but it's not handled here.
def _pace6000_io(num, cmd) '{
    local aux, retval, nb, rsvp

    _pace6000_debug ">>> _pace6000_io                 " cmd
    if (index( cmd, "?") )  { # is there an answer requested ?
        _pace6000_debug "RSVP"
        rsvp = 1
    }

    if (!__PACE6000["SIMUL"]) {
        if (! __PACE6000[num]["dev"]) {
            eprint "pace6000: Apparently the config didn`t work. Please reconfig!"
            exit
        }
        if (__PACE6000[num]["type"] == "ethernet") {
            sock_par(__PACE6000[num]["dev"], "flush") # clean trailing output
            sock_put(__PACE6000[num]["dev"], sprintf("%s\n", cmd))
            if (rsvp) {
                retval = sock_get(__PACE6000[num]["dev"], "\n")
                _pace6000_debug "raw data", retval
                # the command is prepended to the answer :-(
                nb = split(retval, aux, " ")
                retval = aux[1]
                if (nb > 1) {
                    local i
                    for (i=2; i<nb; i++)
                        retval = retval " " aux[i]
                }
            } else
                return
        } else if (__PACE6000[num]["type"] == "serial") {
            cdef("cleanup_once", sprintf("_pace6000_flush %d;",num),"_pace6000")
            ser_put(__PACE6000[num]["dev"], sprintf("%s\r\n", cmd))
            if (rsvp) {
                retval = ser_get(__PACE6000[num]["dev"], "\r")
                nb = split(retval, aux, PACE5000[num]["split"])
                if (nb > 1)
                    retval = aux[1]*1
            } else
                return
        }
    }
    else { # simulation !
        retval = "0.1"
    }
    _pace6000_debug ">>> _pace6000_io returns         " retval
    return retval
}
'


#%IU% ()
#%MDESC%get errors
#%BR%returns ".error." if there is an error
#%BR%returns 0 if no error
def _pace6000_report_error(unit) '{
    local syserr, esr, osr, sre, str, errflag
    if ((esr = _pace6000_io(unit, "*ESR?")*1) != 0) {
#        if ((esr >> 5) & 1) str = str " Command error" # doubles syserr
        if ((esr >> 4) & 1) str = str " Execution error"
        if ((esr >> 2) & 1) str = str " Query errors"
        eprint "PACE6000 ESR error:" str
        errflag  = 1
    }
    if ((sre = _pace6000_io(unit, "*STB?")*1) != 0) { # Status Register
        if (sre & (1<<2)) # bit 2 ! ignore bit 4, which is just normal output
            if ((syserr = _pace6000_io(unit, ":SYST:ERR?")) != "0, No error") {
                local nb, aux
                nb = split(syserr, aux, ",")
                eprint "PACE6000 STB error:", aux[1]
                errflag  = 1
            }
        else if (sre & (1<<7)) # bit 7, Operation Status (16 bits wide)
            if ((osr = _pace6000_io(unit, ":STATus:OPERation:CONDition?")*1) != 0) {
                if ( osr       & 1) str = str " Vent complete"
                if ((osr >> 1) & 1) str = str " Range change complete"
                if ((osr >> 2) & 1) str = str " In-limits reached"
                if ((osr >> 3) & 1) str = str " Zero complete"
                if ((osr >> 4) & 1) str = str " Auto-zero started"
                if ((osr >> 5) & 1) str = str " Fill time, timed-out"
                if ((osr >> 8) & 1) str = str " contacts changed state"
                eprint "PACE6000 status error:" str
                errflag  = 1
            }
    }
    if (errflag)
        return ".error"
    else
        return ""
}
'


#%UU%<mnemonic> <on/off>
#%MDESC%switch controller on or off
def pace6000_switch '{
    local unit, chan, onoff, currstate, answer, mnum, ctrl, cmd
    # on and off are builtin functions, which cause an error with
    # _check0. So if $1 is on or off, that`s wrong.
    if (($# < 1) || ("$1" == "on") || ("$1" == "off")) {
        print "Usage:  pace6000_switch <mnemonic> [\"on/off\"]"
        exit
    }
    mnum = $1 * 1
    if (mnum != motor_num(mnum)) {
        eprint "$0: Invalid motor name:  $1"
        exit
    }
    if ((ctrl = motor_par(mnum, "device_id")) != "pace6000") {
        eprint "$0: this motor is not a controlled by a pace5000/6000!"
        exit
    }
    unit = motor_par("$1", "unit")
    chan = motor_par("$1", "channel") + 1
    _pace6000_debug "Switch controller on/off - motor:", "$1", unit

    cmd = ":OUTPut" chan ":STATe?"
    currstate = _pace6000_io(unit, cmd) * 1
    print "PACE controller output is", currstate ? "ON" : "OFF"
    if ($# < 2) {
        state = currstate ? 0 : 1
        if (!yesno(sprintf("Do you want to change this to %s", state ? "ON" : "OFF"), 1)) {
            exit
        } else {
            onoff = onoff ? 0 : 1
        }
    } else {
        onoff = "$2"
        if ( onoff == "ON" || onoff == "on" || onoff == "On") onoff = 1
        else if ( onoff == "OFF" || onoff == "off" || onoff == "Off") onoff = 0
        else {
            print "Value", "$2", "is not allowed as second argument!"
            exit
        }
    }
    if (currstate != onoff) {
        cmd = ":OUTPut" chan ":STATe " onoff
        _pace6000_io(unit, cmd)
        cmd = ":OUTPut" chan ":STATe?"
        currstate = _pace6000_io(unit, cmd) * 1
        print "and is now", currstate  ? "ON" : "OFF"
    }
}
'
def pace_switch  'pace6000_switch'


#%UU%mnemonic <slewrate>
#%MDESC%Set the rate at which the controller ramps pressure. Documentation says
#values can also be "max" or "min", but that didn't work for me.
def pace6000_slewrate '{
    local unit, answer, cmd, slewrate
    if ($# < 1) {
        print "Usage:  $0 mnemonic <slewrate>"
        exit
    }
   _check0 # check motor
    if ((ctrl = motor_par($1, "device_id")) != "pace6000") {
        eprint "$0: this motor is not a controlled by a pace5000/6000!"
        exit
    }
    unit = motor_par("$1", "unit")
    chan = motor_par("$1", "channel") + 1
    cmd = ":UNIT" chan ":PRESsure?"
    instrunit = _pace6000_io(unit, cmd)
    instrunit = substr(instrunit, 1, length(instrunit) - 2 )
    cmd = ":SOURCE" chan ":PRESsure:SLEW?"
    # :SOURCE[x][:PRESsure]:SLEW?
    # where x = 1 or 2 is the module number (default - 1)
    # Short form:
    # :SOUR:SLEW?
    # Function:
    # Query rate value
    # Response:
    # Decimal number representing rate value in selected units/second.

    # convert to unit / minute
    slewrate = _pace6000_io(unit, cmd) * 1
    print "PACE controller channel", chan, "slewrate was", slewrate, instrunit "/sec,", slewrate *60, instrunit "/min"
    slewrate *= 60
    if ($# < 2) {
        slewrate = getval("Do you want to change this", slewrate,  sprintf(" %s/min",instrunit))
    } else {
        slewrate = "$2" * 1.0
    }
    slewrate /= 60
    cmd = ":SOURCE" chan ":PRESsure:SLEW " slewrate
    _pace6000_io(unit, cmd)
    cmd = ":SOURCE" chan ":PRESsure:SLEW?"
    slewrate = _pace6000_io(unit, cmd) * 1
    print "PACE controller channel", chan, "slewrate now is", slewrate, instrunit "/sec,", slewrate *60, instrunit "/min"
}
'
def pace_slewrate  'pace6000_slewrate'

#%MACROS%
#%IMACROS%
#%LOG%
#$Revision: 1.6 $, $Date: 2015/06/02 13:12:56 $
#
#%AUTHOR% mcd january 2011, hw - move to SCPI in Jan 2013
#%TOC%