esrf

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

#%TITLE% HEXAPODSM.MAC
#%NAME% 
#   Macros to control SMARACT piezo hexapod from Spec.
#
#%DESCRIPTION%
#   This macro set allows you to define macro motors for each
#   hexapod virtual axis. Translation axis are in mm and 
#   rotations in degree
#
#   First, declare an hexapod controller in config.
#   The ADDR field must be set to the network hostname of the
#   SMARACT MCS controller:
#
#   MOTORS    DEVICE    ADDR   <>MODE      NUM               <>TYPE
#%BR%   YES       hexasm    smhexid06            6         Macro Motors
#
#   Configure then motors in config referring to that controller. 
#   Channel assignment will decide on the motor role as follow:
#
#%PRE%
#   Channel
#     0   -   X      
#     1   -   Y     
#     2   -   Z
#     3   -   RotX
#     4   -   RotY
#     5   -   RotZ
#
#%PRE%
#%END%


#
#
#
global HEXASM[]
global HEXASM_CHS[]
HEXASM_CHS[0] = "X"
HEXASM_CHS[1] = "Y"
HEXASM_CHS[2] = "Z"
HEXASM_CHS[3] = "RotX"
HEXASM_CHS[4] = "RotY"
HEXASM_CHS[5] = "RotZ"


#%IU%(mne, type, unit, module, channel)
#%MDESC%
# Macro motor implementation
#
def hexasm_config(mne, type, p1, p2, p3) '{
    local dev
    local ans

    #
    # p1==controller index p2==number of motors supported
    #
    if (type == "ctrl") {
        # retrieve the MCS controller hostname
        dev = hexasm_ADDR

        # the communication port is fixed for MCS controller
        if(!index(dev, ":")) {
            dev = sprintf("%s:2000", dev)
        }

        # check access to controller 
        ans = _hexasm_putget(dev, "%info device")
        if(index(ans, ".error.")) {
            _hexasm_err
            printf("unable to reach \"%s\"\n", dev)
            return ".error."
        }
        printf("Using SMARACT HEXAPOD controller \"%s\"\n", \
            substr(dev, 0, index(dev,":")-1))


        # valid controller at this point
        if(!(whatis("HEXASM") & 0x1000000))  {
            list_init HEXASM
        }
        list_add(HEXASM, hexasm_ADDR)
        list_setpar(HEXASM, hexasm_ADDR, "dev", dev)
    }


    #
    # p1==unit p2==module p3==channel
    #
    if (type == "mot") {
        local ch
        local idx

        # minimum check
        ch = p3
        if((ch<0) || (ch>6)) {
            _hexasm_err
            printf("invalid channel, must be <=6\n")
            return ".error."
        }
        idx = sprintf("mne_%s", HEXASM_CHS[ch])
        list_setpar(HEXASM, hexasm_ADDR, idx, motor_mne(mne))
    }
}'


#%IU%(mne, cmd, p1, p2)
#%MDESC%
# Macro motor implementation
#
def hexasm_cmd(mne, cmd, p1, p2) '{
    local ans
    local dev
    local ch
    local pos
    local idx

    # trust that the configuration has been done by SPEC
    dev = list_getpar(HEXASM, hexasm_ADDR, "dev")

    #
    #
    #
    if (mne == ".." ) {

        #
        #
        if ( cmd == "preread_all" ) {
            # TODO: optimization needed but "preread_all" can not
            # be trusted, not always called by SPEC
            #return(_hexasm_preread_all(dev))
        }


        #
        #
        if ( cmd == "start_all" ) {
            local cmd

            # gather all target positions
            cmd = "mov"
            for(ch=0;ch<6;ch++) {
                idx = sprintf("tgt_%s", HEXASM_CHS[ch])
                pos = list_getpar(HEXASM, hexasm_ADDR, idx)

                # current unit is mm for translations and
                # degrees for rotations
                # TODO: allow user to choose translations unit in config
                cmd = sprintf("%s %f%s", cmd, pos, (ch<3)?"m":"")
            }

            # launch the motion
            ans = _hexasm_putget(dev, cmd)
            if(index(ans, ".error.")) {
                _hexasm_err
                printf("unable to launch motion: %s\n", substr(ans, 8))
                return(".error.")
            }

            return
        }


        #
        #
        if ( cmd == "prestart_all") {

            # ensure to have a target postion for all axes even those
            # that will not be moved 
            for(ch=0;ch<6;ch++) {
                # the "preread_all" has be called at least once on reconfig
                idx = sprintf("pos_%s", HEXASM_CHS[ch])
                pos = list_getpar(HEXASM, hexasm_ADDR, idx)
                idx = sprintf("tgt_%s", HEXASM_CHS[ch])
                list_setpar(HEXASM, hexasm_ADDR, idx, pos)
            }
        }

        # called on <ctrl-c> but after the abort_one on each concerned motor
        if ( cmd == "abort_all" ) {
            # ignore errors
            _hexasm_putget(dev, "stop")
            return
        }

        return
    }


    # from here handle single motor call
    ch = motor_par(mne, "channel")


    #
    #
    #
    if(cmd == "position") {

        # TODO: found a way to optimize the number of calls
        if(index(_hexasm_preread_all(dev), ".error.")) {
            return(".error.")
        }
        
        idx = sprintf("pos_%s", HEXASM_CHS[ch])
        pos = list_getpar(HEXASM, hexasm_ADDR, idx)
        return(sprintf("%.15g", pos))
    }


    #
    #
    #
    if(cmd == "get_status") {
        local ret

        # no motion by default
        ret = 0

        # get move status from controller:
        # 0 == stopped
        # 1 == holding positions actively, but not moving
        # 2 == moving
        ans = _hexasm_putget(dev, "mst?")
        if(index(ans, ".error.")) {
            _hexasm_err
            printf("unable to read status: %s\n", substr(ans, 8))
            return(ret)
        }

        # at least one leg is still moving
        if(int(ans) == 2) {
            ret = 0x2
            return(ret)
        }

        return(ret)
    }


   #
   # p1==abs pos, p2==rel pos, with pos in mm or deg
   #
   if ( cmd == "start_one") {
        idx = sprintf("tgt_%s", HEXASM_CHS[ch])
        list_setpar(HEXASM, hexasm_ADDR, idx, p1) 
        return
   }
}'


#%IU%(dev)
#%MDESC%
# Macro motor implementation
#
def _hexasm_preread_all(dev) '{
    local ans
    local ch
    local pos
    local idx
    local tt[]

    ans = _hexasm_putget(dev, "pos?")
    if(index(ans, ".error.")) {
        _hexasm_err
        printf("unable to read positions: %s\n", substr(ans, 8))
        return(".error.")
    }
    
    if(split(ans, tt) != 6) {
        _hexasm_err
        printf("unable to parse positions\n")
        return(".error.")
    }

    # store positions for afterward
    for(ch=0;ch<6;ch++) {
        idx = sprintf("pos_%s", HEXASM_CHS[ch])
        sscanf(tt[ch], "%f", pos)

        # warning: the translation default unit is meters
        # and the macro motors unit is mm
        if(ch<3) {
            pos = pos * 1000.0
        }
        list_setpar(HEXASM, hexasm_ADDR, idx, pos)
    }
    return ""
}'


#%UU% [hexapod-number]
#%MDESC%
# Opens an interactive menu for hexapod control
#
def hexasm'{
    local hxn addr
    local n menu[] cmds[]
    local i ret

    n++
    menu[n] = "Show hexapod information"
    cmds[n] = "hexasmshow"

    n++
    menu[n] = "Dump hexapod controller internals"
    cmds[n] = "hexasmdump"

    n++
    menu[n] = "Hardware Initialization and Ref. Search"
    cmds[n] = "hexasmhwinit"

    n++
    menu[n] = "Set new pivot point"
    cmds[n] = "hexasmpivot"

    if($#) {
        hxn=$1
    } else {
        hxn=_hexasmselect()
    }
    addr = list_item(HEXASM, hxn)

    for(;;) {
        tty_cntl("cl")

        tty_cntl("md")
        printf("\n   <%s>\n",addr)
        printf("\n   Commands: \n\n")
        tty_cntl("me")

        for(i=1;i<=n;i++) {
            tty_cntl("md");printf("   %d - ",i);tty_cntl("me")
            printf("%s\n",menu[i])
        }
        tty_cntl("md");printf("\n   0 - ");tty_cntl("me")
        printf("Quit\n")

        ret = getval("\n\n   Command? ", 0)
        if(!ret) {
            break
        }

        if((ret<1) || (ret>n)) {
            continue
        }

        printf("\n")
        eval(sprintf("%s %d", cmds[ret], hxn))
        break
    }
}'

#%UU% [hexapod-number]
#%MDESC%
# Same as "hexasm" and just for users used to standard hexapod macros
#
def hexasmmenu 'hexasm'


#%UU% [hexapod-number]
#%MDESC%
# Does a full reset on one hexapod
#
def hexasmshow '{
    local dev
    local addr
    local hxn
    local val
    local ch
    local idx
    local w1 w2
    local tt[]

    if($#) {
        hxn=$1
    } else {
        hxn=_hexasmselect()
    }

    if((hxn<1) || (hxn>list_n(HEXASM))) {
        print "Wrong hexapod selected"
        exit
    }

    addr = list_item(HEXASM, hxn)
    dev  = list_getpar(HEXASM, addr, "dev")


    #
    #
    #
    tty_cntl("md")
    printf("\n   <%s>\n\n",addr)
    tty_cntl("me")

    w1 = -20

    #
    #
    #
    tty_cntl("md")
    printf("   %*s", w1, "Axes referenced: ")
    tty_cntl("me")
    ans = _hexasm_putget(dev, "ref?")
    printf("%s\n", (int(ans) == 1)?"yes":"no")


    #
    #
    #
    tty_cntl("md")
    printf("   %*s", w1, "Axes velocity: ")
    tty_cntl("me")
    ans = _hexasm_putget(dev, "vel?")
    sscanf(ans, "%f", val)
    printf("%.4f (mm/s)\n", val*1000.0)


    #
    #
    #
    tty_cntl("md")
    printf("   %*s", w1, "Axes acceleration: ")
    tty_cntl("me")
    ans = _hexasm_putget(dev, "acc?")
    sscanf(ans, "%f", val)
    printf("%.4f (mm/s2)\n", val*1000.0)


    #
    #
    #
    tty_cntl("md")
    printf("   %*s", w1, "Pivot point X/Y/Z: ")
    tty_cntl("me")
    ans = _hexasm_putget(dev, "piv?")
    if(split(ans, tt) != 3) {
        _hexasm_err
        printf("unable to parse pivot positions\n")
        return(".error.")
    }
    for(ch=0;ch<3;ch++) {
        printf("%.4f ", tt[ch]*1000.0)
    }
    printf("(mm)\n")

    tty_cntl("md")
    printf("   %*s", w1, "Pivot mode: ")
    tty_cntl("me")
    ans = _hexasm_putget(dev, "pvm?")
    printf("%s\n", int(ans)?"fixed":"relative")


    #
    #
    #
    w1 = -8 
    w2 = -8
    printf("\n")
    tty_cntl("md")
    printf("   %*s    %*s    %10s\n", \
        w1, "Axis:", w2, "Motor:", "Position:")
    tty_cntl("me")
    for(ch=0;ch<6;ch++) {
        idx = sprintf("mne_%s", HEXASM_CHS[ch])
        mne = list_getpar(HEXASM, addr, idx)
        if(mne == -1) {
            mne = ""
        }
        idx = sprintf("pos_%s", HEXASM_CHS[ch])
        pos = list_getpar(HEXASM, addr, idx)
        printf("   %*s    %*s    %10.6f (%s)\n", \
            w1, HEXASM_CHS[ch], w2, mne, pos, (ch<3)?"mm":"deg")
    }
}'



#%UU% [hexapod-number]
#%MDESC%
# Dump hexapod controller internal informations
#
def hexasmdump '{
    local dev
    local addr
    local hxn
    local ans

    if($#) {
        hxn=$1
    } else {
        hxn=_hexasmselect()
    }

    if((hxn<1) || (hxn>list_n(HEXASM))) {
        print "Wrong hexapod selected"
        exit
    }

    addr = list_item(HEXASM, hxn)
    dev  = list_getpar(HEXASM, addr, "dev")


    #
    #
    #
    tty_cntl("md")
    printf("\n   <%s>\n\n",addr)
    tty_cntl("me")


    #
    #
    #
    ans = _hexasm_putget(dev, "%info device")
    printf("%s\n\n", ans)

    ans = _hexasm_putget(dev, "%info network")
    printf("%s\n\n", ans)

    ans = _hexasm_putget(dev, "%info status")
    printf("%s\n\n", ans)

    ans = _hexasm_putget(dev, "%info units")
    printf("%s\n\n", ans)
}'


#%UU% [hexapod-number]
#%MDESC%
# Does a full reset on one hexapod
#
def hexasmhwinit '{
    local dev
    local addr
    local hxn
    local ans
    local yn

    if($#) {
        hxn=$1
    } else {
        hxn=_hexasmselect()
    }

    if((hxn<1) || (hxn>list_n(HEXASM))) {
        print "Wrong hexapod selected"
        exit
    }

    addr = list_item(HEXASM, hxn)
    dev  = list_getpar(HEXASM, addr, "dev")

    tty_cntl("md");printf("\n           WARNING! WARNING!\n");tty_cntl("me")
    printf("  A hard reset on the hexapod will move \n")
    printf("    the hexapod to its limit switches.\n")
    printf("      !!  This may be dangerous !!.\n")
    tty_cntl("md");printf("           WARNING! WARNING!\n\n");tty_cntl("me")
    yn = yesno("\nDo you want to continue",0)
    if(!yn) {
        exit
    }

    printf("Launched....")

    # blocking call
    ans = _hexasm_putget(dev, "ref")
    if(index(ans, ".error.")) {
        printf("\n")
        _hexasm_err
        printf("unable to launch motion: %s\n", substr(ans, 8))
        exit
    }

    # check result
    ans = _hexasm_putget(dev, "ref?")
    if(index(ans, ".error.")) {
        printf("\n")
        _hexasm_err
        printf("unable to launch motion: %s\n", substr(ans, 8))
        exit
    }

    # 
    printf("%s\n", (int(ans) == 1)?"ok":"fail")
        
    # force motor positions update silently
    read_motors(0x06)
}'


#%UU% [hexapod-number]
#%MDESC%
# Set a new pivot point
#
def hexasmpivot '{
    local dev
    local addr
    local hxn
    local cmd
    local old[]   new[]
    local old_fix new_fix
    local yn 
    local ch

    if($#) {
        hxn=$1
    } else {
        hxn=_hexasmselect()
    }

    if((hxn<1) || (hxn>list_n(HEXASM))) {
        print "Wrong hexapod selected"
        exit
    }

    addr = list_item(HEXASM, hxn)
    dev  = list_getpar(HEXASM, addr, "dev")

    #
    #
    #
    tty_cntl("md")
    printf("\n   <%s>\n\n",addr)
    tty_cntl("me")

    ans = _hexasm_putget(dev, "piv?")
    if(split(ans, old) != 3) {
        _hexasm_err
        printf("unable to parse pivot positions\n")
        return(".error.")
    }

    # position are given in meters
    for(ch=0;ch<3;ch++) {
        old[ch] *= 1000.0
    }

    ans = _hexasm_putget(dev, "pvm?")
    old_fix =  int(ans)

    new[0]  = getval("   X  : ", old[0])
    new[1]  = getval("   Y  : ", old[1])
    new[2]  = getval("   Z  : ", old[2])
    new_fix = getval("   fix: ", old_fix)

    yn = yesno("\nDo you want to continue",0)
    if(!yn) {
        exit
    }

    cmd = "piv"
    for(ch=0;ch<3;ch++) {
        cmd = sprintf("%s %fm", cmd, new[ch])
    }

    ans = _hexasm_putget(dev, cmd)
    if(index(ans, ".error.")) {
        _hexasm_err
        printf("unable to set new pivot point: %s\n", substr(ans, 8))
        return(".error.")
    }

    cmd = sprintf("pvm %d", int(new_fix)?1:0)
    ans = _hexasm_putget(dev, cmd)
    if(index(ans, ".error.")) {
        _hexasm_err
        printf("unable to set new pivot mode: %s\n", substr(ans, 8))
        return(".error.")
    }

    printf("done\n")
}'


#%IU%
#%MDESC%
#  Utility macro to select which hexapod to use if several ones
#  have been configured.
#
def _hexasmselect() '{
    local hxno hx

    hxno = list_n(HEXASM)

    if (hxno == 1) {
       return(1)
    } else if (hxno > 1 ) {
       printf("Select hexapod from list\n")
       for (hx = 1 ; hx <= hxno ; hx++) {
           printf("  %2d - %18s  ",hx,(hxname=list_item(HEXASM,hx)))
           printf("\n")
       }
       return(getval("\n  Number: ",1))
    } else {
       printf("No hexapod defined. Sorry\n")
       return(-1)
    }
}'


#%IU%(dev, cmd)
#%MDESC%
# Returns string ".error.xxxxx." in case of timeout or command failure.
#
def _hexasm_putget(dev, cmd) '{
    local ans
    local ret
    local err
    local msg
    local ln lc

    ans = _hexasm_put(dev, cmd)
    if(ans != "") {
        return(ans)
    }
    for(ret="";;) {
        # blocking call
        ans = sock_get(dev, "\r\n")

        # handle timeout
        if(ans == "") {
            ret = ".error.timeout."
            break
        }
   
        # handle controller error code
        if(sscanf(ans, "\!%d", err) == 1) {

            # no error 
            if(!err) {
                return(ret)
            }
        
            # decode error code
            msg = _hexasm_putget(dev, sprintf("%%code? %d", err))
            ret = sprintf(".error.%s.", msg)
            break
        }

        # valid line at this point
        ret = sprintf("%s%s", ret, ans)

        # handle multiline answer
        if(!sock_par(dev, "queue")) {
            break
        }
    }

    # remove terminator
    for(;;) {
        ln = length(ret)
        lc = substr(ret, ln, 1) 
        if((lc == "\r") || (lc == "\n")) {
            ret = substr(ret, 0, ln-1)
            continue
        }
        break
    }
  
    return(ret)
}'


#%IU%(dev, cmd)
#%MDESC%
# Returns string ".error.xxxxx." in case of timeout or command failure.
#
def _hexasm_put(dev, cmd) '{
    local ans
    local ret

    if(!index(cmd, "\n")) {
        cmd=cmd"\n"
    }

    # just in case
    sock_par(dev, "flush")

    ret = ""
    ans = sock_put(dev, cmd)
    if(ans == -1) {
        ret = ".error.network." 
    }

    return(ret)
}'


#%IU%(dev, cmd)
#%MDESC%
# Cosmetics macro
#
def _hexasm_err '{
    tty_cntl("md")
    printf("HEXA SMARTACT ERROR: ")
    tty_cntl("me")
}'


#%MACROS%
#%IMACROS%
#%AUTHOR% MP / BLISS / (Original May2017)
#%BR%$Revision: 1.2 $ / $Date: 2017/05/12 10:53:18 $
#%TOC%