esrf

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

######################################################################
#############                                             ############
#############  macros for PSD 8 Hamilton syringe drive.   ############
#############                                             ############
######################################################################
# Fri 22 Feb 2008 13:32:13
# Wed  7 Nov 2012 16:26:53
# 2014/10/10 rh - updated changes made by CG for id03 and fixed for ID15


#%TITLE%
#%NAME%
#%END%

#%DESCRIPTION%

#%EXAMPLE%


######################################################################
##########################                  ##########################
##########################  SETUP AND INIT  ##########################
##########################                  ##########################
######################################################################

need hg
hg_config_style("psd8", 1, 2, 3)
hg_generate("psd8")


#%UU% <serial_number> <syringe1_model> [<syringe2_model>]*
#%MDESC%
#  This macro does the setup procedure to use one or more PSD8 hamilton syringe.
#  Example to setup 3 syringes pluged on serial line 17.
#     psd8_setup 17 1001 1005 1025
#
def psd8_setup '{
    global PSD8_PAR[]
    global PSD8_ARGS

    unglobal  PSD8_CONF
    global    PSD8_CONF
    list_init PSD8_CONF



    local _model  _volume  _nos
    local res
    local nbparam




    nbparam = $#
    

    # PSD8_PAR["serial"]  = Serial line number in spec.
    # PSD8_PAR["on"]      = PSD control active or not.
    # PSD8_PAR["timeout"] = Timeout value (in seconds).

    #### PSD8_PAR[1]["rate"] = PSD_PAR[1]["Syringe"]/PSD_PAR[1]["MaxVelocity"]
    #### PSD8_PAR[1]["volume"] = the current volume of solution in syringe

    # PSD8_PAR[1]["maxvelocity"] = 1.2-600 (table 8-33)
    # PSD8_PAR[1]["speedcode"]   = 1-40 (table 8-33)
    # PSD8_PAR[1]["maxsteps"]    = 3000 (standard) or 24000 (hi-resolution)
    # PSD8_PAR[1]["port"]        = 1,2,3... (depend on the valve model)
    # PSD8_PAR[1]["model"]       = model number, in {1001 ; 1002 ; 1005 ; 1010 ; 1025}

    # Volume of the syringe given the model number.

    PSD8_PAR["volume"]["1001"] = 1
    PSD8_PAR["volume"]["1002"] = 2.5
    PSD8_PAR["volume"]["1005"] = 5
    PSD8_PAR["volume"]["1010"] = 10
    PSD8_PAR["volume"]["1025"] = 25

    # General parameters or constants.
    PSD8_PAR["model_string"]     = "1001 1002 1005 1010 1025"
    PSD8_PAR["timeout"]          = 10
    PSD8_PAR["serial"]           = 0
    PSD8_PAR["maxsteps"]         = 3000
    PSD8_PAR["maxsteps_highres"] = 24000
    PSD8_PAR["sleep_time"]       = 0.2

    if(nbparam == 0){
        print "psd8_setup usage:"
        print "  psd8_setup <serial> [<model>]*"
        print " "
        print " NB: configuration of serial line : "
        print "   -9600 or 38400 bps"
        print "   -8  data bits"
        print "   -No parity"
        print "   -1  stop bit"
        exit
    }

    if(nbparam < 2) {
        psd8_err("psd8_setup need more than 1 arguments")
        exit
    }

    if ($1 < 0) {
        psd8_err("serial line number must be positif")
        exit
    }

    if ($1 > 32){
        psd8_err("Achtung : the serial line number is strange (>32)")
    }

    PSD8_PAR["serial"] = $1

    res = split ("$*", PSD8_ARGS)

    _nos = res-1

    psd8_msg("----------- PSD8 syringe(s) setup -----------")

    for (i=0 ; i<_nos ; i++){
        _model  = PSD8_ARGS[i+1]
        _volume = PSD8_PAR["volume"][_model]
        psd8_msg(sprintf( "setup syringe %d : model=%s volume=%g mL", \
                          i+1, _model,   _volume))

        if (!index(PSD8_PAR["model_string"], _model)){
            psd8_err(sprintf("syringe %d is not a valid model", i+1))
            exit
        }

        _name = sprintf("syringe%d", i+1)
        list_add(PSD8_CONF, _name)
        list_setpar(PSD8_CONF, _name, "model",   _model)
        list_setpar(PSD8_CONF, _name, "volume",  _volume)
        list_setpar(PSD8_CONF, _name, "address", i+1)
    }

    psd8_msg("---------------- setup done -----------------")

    psd8_init_all()

    psd8_on
}'




#%UU% <parma>
#%MDESC%
#    
def psd8_unsetup'{
    psd8_off
    unglobal PSD8_PAR
    unglobal PSD8_ARGS
    unglobal PSD8_CONF
}'



#%UU% <parma>
#%MDESC%
#    
def psd8_on '{
    global PSD8_PAR[]

    PSD8_PAR["on"] = 1
    psd8_msg("PSD8 is now on")
}'


#%UU% <parma>
#%MDESC%
#
def psd8_off '{
    global PSD8_PAR[]

    PSD8_PAR["on"] = 0
    psd8_msg("PSD8 is now off")
}'


#%IU% <parma>
#%MDESC%
#   Calls psd8_init for all syringes.
def psd8_init_all() '{
    local i, _syr_num, _nos

    _nos = list_n(PSD8_CONF)

    for (i=0; i<_nos; i++){
        _syr_num = i+1
        psd8_init(_syr_num)
    }
}'


#%IU% <parma>
#%MDESC%
#    Init of one syringe pump.
def psd8_init(syr_num) '{
    global PSD8_PAR[]
    global PSD8_CONF[]

    local _pos  _name

    _name = PSD8_CONF[syr_num]

    # + initialization
    #  - syringe volume/full stroke (global variable)
    #  - with or without valve configuration
    #  - at [full/half/quarter/...] speed
    #  - set motor resolution

    # Flush (in and out) the serial line.
    ser_par(PSD8_PAR["serial"], "flush", 2)

    # initialize PSD8 assign valve output to right
    # _psd8_send(syr_num, "ZR")

    # initialize PSD8 assign valve output to left
    # _psd8_send(syr_num, "XR")

    # initialize PSD8: configure for no valve
    _psd8_send(syr_num, "WR")


}'


######################################################################
#############################             ############################
#############################  MOVEMENTS  ############################
#############################             ############################
######################################################################


# Moves given syringe to absolute position <position>.
def psd8_mv(syr_num, position) '{
    global PSD8_PAR[]
    local _maxsteps

    _maxsteps = _psd8_get_maxsteps(syr_num)

    if (( position < 0 )  ||  (position > _maxsteps)){
        psd8_err(sprintf (" move out of range : %d.  Range is [0..%d]", \
                          position , _maxsteps ))
        exit
    }

    _psd8_send(syr_num, sprintf ("A%dR", position))
}'


# Move given syringe of <movement> steps.
# + to fill
# - to push out
def psd8_mvr(syr_num, movement) '{
    global PSD8_PAR[]
    local _maxsteps _position

    _maxsteps = _psd8_get_maxsteps(syr_num)
    _position = _psd8_get_position(syr_num)

    if ((_position + movement) > _maxsteps) {
        psd8_err(sprintf (" move out of range : %d. Range is [0..%d]\n", \
                          _position+movement, PSD8_PAR["maxsteps"] ))
        exit
    }

    # D -> up.   => negative move to dispense.
    # P -> down. => positif  move to draw/pickup.
    if(movement < 0 ){
        psd8_dbg(sprintf("psd8_mvr : D %d", -movement))
        _psd8_send(syr_num, sprintf ("D%dR", -movement))
    }
    else{
        psd8_dbg(sprintf("psd8_mvr : P %d", movement))
        _psd8_send(syr_num, sprintf ("P%dR", movement))
    }
}'


#%UU% (<syr_num>, <volume>)
#%MDESC%
#    Fills <volume> ml of liquid into the syringe.
def psd8_draw(syr_num, volume)'{
    global PSD8_PAR[]

    local volume_inside
    local vol_max
    local delta_steps
    local _name
    local _position

    if (volume < 0){
        psd8_err("psd8_draw - volume must be positif")
        exit
    }

    _name = PSD8_CONF[syr_num]

    # test position
    _position = _psd8_get_position(syr_num)
    volume_inside = _psd8_steps_to_volume(syr_num, _position)
    vol_max       = PSD8_CONF["syringe1"]["volume"] 
    psd8_dbg(sprintf("_name=%s volume_inside=%f vol_max=%g",\
                     _name, volume_inside, vol_max ))

    if (volume_inside + volume > vol_max){
        psd8_err("Achtung: You syringe is too small")
        exit
    }
    else{
        delta_steps = _psd8_volume_to_steps(syr_num, volume)
        psd8_mvr(syr_num, delta_steps)
        return (0)
    }
}'


# Pushout/dispense <volume> mL of liquid from the syringe <syringe_number>.
def psd8_dispense(syr_num, volume) '{
    global PSD8_PAR[]

    local _volume_inside
    local _delta_steps
    local _name

    if (volume<0){
        psd8_err("volume must be positif")
        exit
    }

    _name = sprintf("syringe%d", syr_num)

    # test position
    _volume_inside = _psd8_steps_to_volume(syr_num,                     \
                                           _psd8_get_position(syr_num))

    psd8_dbg(sprintf("psd8_dispense : _name=%s volume_inside=%f volume=%g",\
                     _name, _volume_inside, volume ))


    if (_volume_inside < volume){
        psd8_err("There is not enough liquid inside syringe")
        exit
    }
    else{
        _delta_steps = _psd8_volume_to_steps(syr_num, volume)
        psd8_dbg(sprintf("psd8_dispense : mvr(-%f)", _delta_steps))
        psd8_mvr(syr_num, -_delta_steps)
        return (0)
    }
}'


######################################################################
###########################                 ##########################
###########################  VALVE ACTIONS  ##########################
###########################                 ##########################
######################################################################

def psd8_valve_to_input_pos(syr_num) '{
    global PSD8_PAR[]
    _psd8_send(syr_num, "IR")
}'

def psd8_valve_to_output_pos(syr_num) '{
    global PSD8_PAR[]
    _psd8_send(syr_num, "OR")
}'

def psd8_valve_to_bypass_pos(syr_num) '{
    global PSD8_PAR[]
    _psd8_send(syr_num, "BR")
}'

def psd8_valve_to_extra_pos(syr_num) '{
    global PSD8_PAR[]
    _psd8_send(syr_num, "ER")
}'


######################################################################
######################                          ######################
######################  MOTOR CONTROL COMMANDS  ######################
######################                          ######################
######################################################################


#%IU% (<syr_num>)
#%MDESC%
#    
def psd8_mot_set_resolution_high(syr_num) '{
    global PSD8_PAR[]
    local _name

    _name = sprintf("syringe%d", syr_num)
    _psd8_send(syr_num, "N1R")
    PSD8_CONF[_name]["maxsteps"] = PSD8_PAR["maxsteps_highres"]

    list_setpar(PSD8_CONF, _name, "resolution",  "high")
}'


#%IU% (<syr_num>)
#%MDESC%
#    
def _psd8_get_maxsteps(syr_num) '{
    local _maxsteps _name

    _name = sprintf("syringe%d", syr_num)

    if(list_getpar(PSD8_CONF, _name, "resolution") == "high"){
        _maxsteps = PSD8_PAR["maxsteps_highres"]
    }
    else{
        _maxsteps = PSD8_PAR["maxsteps"]
    }

    return(_maxsteps)

}'


#%IU% (<syr_num>)
#%MDESC%
#    
def psd8_mot_set_resolution_normal(syr_num) '{
    global PSD8_PAR[]
    local _name

    _name = sprintf("syringe%d", syr_num)

    _psd8_send(syr_num, "N0R")
    PSD8_CONF[_name]["maxsteps"] = PSD8_PAR["maxsteps"]

    list_setpar(PSD8_CONF, _name, "resolution",  "normal")
}'


# <speed> in [1; 40]
def psd8_mot_set_speed(syr_num, speed) '{
    global PSD8_PAR[]
    global PSD8_CONF

    local _name

    _name = sprintf("syringe%d", syr_num)

    if((speed<0) || (speed>40)){
        psd8_err("speed outside range [0;40]")
        exit
    }

    _psd8_send(syr_num, sprintf("S%dR", speed))

    PSD8_CONF[_name]["speed"] = speed
}'


######################################################################
###############################         ##############################
###############################  UTILS  ##############################
###############################         ##############################
######################################################################


# Converts <steps> to volume for syringe <syr_num>
def _psd8_steps_to_volume(syr_num, steps) '{
    global PSD8_PAR
    global PSD8_CONF

    local _volume
    local _vol_max
    local _maxstep
    local _name

    _name = sprintf("syringe%d", syr_num)

    _vol_max = PSD8_CONF[_name]["volume"]
    _maxstep = _psd8_get_maxsteps(syr_num)

    if (_maxstep == 0) {
        psd8_err("error : _maxstep=0 ")
        exit
    }

    _volume = steps * _vol_max / _maxstep
    return (_volume)
}'


# Converts <volume> to steps for syringe <syr_num>
def _psd8_volume_to_steps(syr_num, volume) '{
    global PSD8_PAR[]

    local _steps
    local _vol_max
    local _maxstep
    local _name

    _name = sprintf("syringe%d", syr_num)

    _vol_max = PSD8_CONF[_name]["volume"]
    _maxstep = _psd8_get_maxsteps(syr_num)

    if(_vol_max == 0) {
        psd8_err(" vol_max=0")
        exit
    }

    _steps = volume * _maxstep / _vol_max
    return (_steps)
}'


# Reads and returns the position (in steps) of a given syringe.
def _psd8_get_position(syr_num) '{
    global PSD8_PAR[]

    local _position  _maxsteps  _answer  _name

    _name = sprintf("syringe%d", syr_num)

    _answer = _psd8_send(syr_num, "?4")

    _position = _answer + 0
    _maxsteps = _psd8_get_maxsteps(syr_num)

    if((_position < 0) || (_position > _maxsteps)) {
        psd8_err(sprintf("The position seems strange : %s", _position))
    }

    return (_position)
}'


# Reads the position and returns the current volume (in mL) of a given syringe.
def _psd8_get_volume(syr_num) '{
    global PSD8_PAR[]

    local _volume
    local _answer

    _answer = _psd8_get_position(syr_num)
    _volume = _psd8_steps_to_volume(syr_num, _answer + 0)

    return (_volume)
}'


######################################################################
###########################                 ##########################
###########################  COMMUNICATION  ##########################
###########################                 ##########################
######################################################################


#
def _psd8_send(syr_num, cmd) '{
    global PSD8_PAR[]

    local _answer   _strcmd  _address  _name

    if(PSD8_PAR["on"]){
        # command form is : /<address><data><CR>

        _name = PSD8_CONF[syr_num]
        _address = PSD8_CONF[_name]["address"]

        # There is a "/" at beginning of command and "\r" at the end:
        _strcmd = sprintf("/%d%s\r", _address, cmd)
#        _strcmd = sprintf("%c%s\r", _address+96, cmd)

        # printf("strcmd =\"%s\"\n", _strcmd)

        ser_par(PSD8_PAR["serial"], "flush", 2)
        ser_put(PSD8_PAR["serial"], _strcmd)

        sleep(PSD8_PAR["sleep_time"])

        _answer = ser_get(PSD8_PAR["serial"], "\r")

        psd8_dbg(sprintf("_psd8_send : _answer = %s", _answer))

        if (_answer == "") {
            psd8_err("no answer from controller")
        }

        # if /0@ =>ok
        global  PSD8_TEMP
        PSD8_TEMP[0] = 0
        split(_answer, PSD8_TEMP, "`")
        PSD8_TEMP[2] = removeEndingChar(PSD8_TEMP[1], "\r")
        PSD8_TEMP[3] = removeEndingChar(PSD8_TEMP[2], "\003")

        return PSD8_TEMP[3]
    }
    else{
        psd8_msg("PSD is OFF")
    }
}'

######################################################################
###############################         ##############################
###############################  INFOS  ##############################
###############################         ##############################
######################################################################

#
#%MDESC%
#    Prints infos for all syringes.
def psd8_info '{
    global PSD8_PAR[]
    global PSD8_CONF[]

    local ii _nos  _name   _syr_num

    _nos = list_n(PSD8_CONF)

    for (ii=0 ; ii<_nos ; ii++){
        _syr_num = ii+1
        psd8_infos(_syr_num)
    }
}'


#
#%MDESC%
#    Prints infos dfor syringe <syr_num>
def psd8_infos(syr_num) '{
    global PSD8_PAR[]

    psd8_msg(sprintf( "FIRMWARE VERSION : %s", _psd8_firmware_version(syr_num)))
    psd8_msg(sprintf( "PUMP STATUS: %s      ", _psd8_pump_status(syr_num)))
    psd8_msg(sprintf( "ABSOLUTE POSITION: %s", _psd8_get_abs_pos(syr_num)))

}'


#%IU% ()
#%MDESC%
#  This macro returns the firmware number of controller.
def _psd8_firmware_version(syr_num) '{
    return _psd8_send(syr_num, "&")
}'

#%IU% ()
#%MDESC%
#  Returns the status of the pump.
def _psd8_pump_status(syr_num) '{
    return _psd8_send(syr_num, "Q")
}'

#%IU% ()
#%MDESC%
#  Returns the absolute syringe position.
def _psd8_get_abs_pos(syr_num) '{
    return  _psd8_send(syr_num, "?")
}'


#%UU%
#%MDESC%
#     Returns 1 if pump is busy 0 otherwise.
def psd8_busy(syr_num) '{
    local _ans

    _ans = _psd8_pump_status(syr_num)

    if(_ans=="/0@\003\r"  ){
        return(1)
    }
    else if (_ans == "/0`\003\r"){
        return(0)
    }
    else {
        psd8_err("unknown status ????")
        return(1)
    }
}'

#      moving :
# 791.ID26CTL> TTT[0] =  _psd8_pump_status(3)
# "trcmd ="/3Q
# 792.ID26CTL> p TTT
# TTT["0"] = "/0@\003\r"


#     not moving:
# 793.ID26CTL> TTT[0] =  _psd8_pump_status(3)
# "trcmd ="/3Q
# 794.ID26CTL> p TTT
# TTT["0"] = "/0`\003\r"



# CYRIL> ser_par(1,"flush",2)
# CYRIL> ser_put(1,"/1XR\r")
# CYRIL> ser_put(1,"/2ZR\r")
# CYRIL> ser_put(1,"/3ZR\r")
# CYRIL> ser_put(1,"/1A1100R\r")
# CYRIL> ser_put(1,"/2A1500R\r")
# CYRIL> ser_put(1,"/3A1900R\r")

# 237.CYRIL> TTT[0] = _psd8_send(1, "?")
# "trcmd ="/1?
# 238.CYRIL> p TTT
# TTT["0"] = "/0`1100\003\r"


# 239.CYRIL> TTT[0] = _psd8_send(2, "?")
# "trcmd ="/2?
# 240.CYRIL> p TTT
# TTT["0"] = "/0`1500\003\r"


# 241.CYRIL> TTT[0] = _psd8_send(3, "?")
# "trcmd ="/3?
# 242.CYRIL> p TTT
# TTT["0"] = "/0`1900\003\r"


# 245.CYRIL> p length(TTT[0])
# 9

# man ascii
# 003   3     03    ETX