esrf

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

#%TITLE% eurotherm2700.mac
#%NAME% TEST macros for a the use of a EuroTherm 2700 temperature controller.
#%CATEGORY% temperature
#%DESCRIPTION%
# Implement macro motors and macro counters to control the regulation loop
# and to read any MODBUS address from the controller.
#
# For the macro counters, the channel is used to defined the MODBUS address
# to read. But as a scaler controller can only have 999 channels, to read
# a higher address, there is an optional counter parameter named "address".
# When MODBUS address to read is defined like than, there is a second
# optional parameter named "floatvalue" to get a float value from
# the controller instead of a rounded temperature value.
#
#$Revision: 1.12 $
#Package bliss version 1.2 is corrupted.

need modbus-rtu.mac

if (!(whatis("__e2700debug")  & 2)) rdef __e2700debug \'#$*\'

#%IU%(unit, fwaddr, value)
#%MDESC%
#
def __e2700_put(unit, fwaddr, value) '{
  if (__E2700[__E2700[unit]]["type"]) {
    return __e2700_put_tango(__E2700[unit], fwaddr, value)
  }
  else {
    return __e2700_put_serial(__E2700[unit], fwaddr, value)
  }
}
'

#%IU%(unit, fwaddr, num, data)
#%MDESC%
#
def __e2700_blockput(unit, fwaddr, num, data) '{
  if (__E2700[__E2700[unit]]["type"]) {
    return __e2700_blockput_tango(__E2700[unit], fwaddr, num, data)
  }
  else {
    return __e2700_blockput_serial(__E2700[unit], fwaddr, num, data)
  }
}
'

#%IU%(unit, fwaddr, num, data)
#%MDESC%
#
def __e2700_get(unit, fwaddr, num, data) '{
  if (__E2700[__E2700[unit]]["type"]) {
    return __e2700_get_tango(__E2700[unit], fwaddr, num, data)
  }
  else {
    return __e2700_get_serial(__E2700[unit], fwaddr, num, data)
  }
}
'

# some modbus functions might need to be declared although not needed, when
# connecting through a Modbus tango server. When working with a ds, it will
# never show, but if needed it is crucial, that people see this!!!
if (!(whatis("modb_rtu_addnode")   & 2)) rdef modb_rtu_addnode()  '{
  eprint "##########################################################"
  eprint "##########################################################"
  eprint "#####                                                #####"
  eprint "#####  Sorry, your lacking the modbus-rtu.mac file!  #####"
  eprint "#####                                                #####"
  eprint "##########################################################"
  eprint "##########################################################"
  return -1
}
'


#%UU%
#%MDESC% toggle debug mode for the present macros.
def e2700_debug '{
  if ((whatis("__e2700debug")>>16) <= 2) { # just a # sign -> off
    rdef __e2700debug "eprint"
    print "e2700 debug is ON"
  } else {
    rdef __e2700debug \'#$*\'
    print "E2700 debug is OFF"
  }
}'

#%IU%(node, fwaddr, value)
#%MDESC%
# Called by spec
def __e2700_put_serial(node, fwaddr, value) '{
  __e2700debug "__e2700_put_serial("  node "," fwaddr "," value ")"
  if (modb_rtu_write_a_word(node, fwaddr, value) < 0) {
    return(-1)
  }
  return(0)
}'

#%IU%(node, fwaddr, value)
#%MDESC%
# Called by spec
def __e2700_blockput_serial(node, fwaddr, nwords, data) '{
  __e2700debug "__e2700_blockput_serial(" node "," fwaddr "," nwords ", data)"
  if (modb_rtu_write_n_words(node, fwaddr, nwords, data) < 0) {
    return(-1)
  }
  return(0)
}'

#%IU%(fwaddr, fwaddr, num, data)
#%MDESC%
# Called by spec
def __e2700_get_serial(node, fwaddr, num, data) '{
  __e2700debug "__e2700_get_serial(" node "," sprintf("0x%x", fwaddr) "," num ", data)"
  if (modb_rtu_read_n_words(node, fwaddr, num, data) < 0) {
    return(-1)
  }
  return(0)
}'


#%IU%(fwaddr, value)
#%MDESC%(server, fwaddr, value)
# Called by spec
def __e2700_put_tango(server, fwaddr, value) '{
  local short array x[2];
  x[0]=fwaddr; x[1]=value;
  __e2700debug "__e2700_put_tango(" server ", " fwaddr ", " value ")"
  tango_io(server, "PresetSingleRegister", x);
  if (TANGO_ERR != "0") {
    eprint "FAILED: Writing", value, "to modbus tag address", fwaddr
#    eprint TANGO_ERR_STACK["0"]["desc"];
    return(-1)
  }
  return(0)
}'

#%IU%(server, fwaddr, nwords, data)
#%MDESC%
# Called by spec
def __e2700_blockput_tango(server, fwaddr, nwords, data) '{
  local short array x[2+nwords]
  x[0]=fwaddr; x[1]=nwords; x[2:] = data
  __e2700debug "__e2700_blockput_tango(" server ", " fwaddr ", " nwords ", data)"
  __e2700debug x
  whats x
  tango_io(server, "PresetMultipleRegisters", x);
  if (TANGO_ERR != "0") {
    eprint "FAILED: Writing", value, "to modbus tag address", fwaddr
#    eprint TANGO_ERR_STACK["0"]["desc"];
    return(-1)
  }
  return(0)
}'


#%IU%(server, fwaddr, num, data)
#%MDESC%
# Called by spec.<BR>data is ushort array
#
def __e2700_get_tango(server, fwaddr, num, data) '{
  local short array x[2], y[num];
  local i, got
  x[0]=fwaddr; x[1]=num;
  __e2700debug "__e2700_get_tango(" server ", " fwaddr ", " num ", data)"
  got = tango_io(server, "ReadHoldingRegisters", x, y);
  if (TANGO_ERR != "0") {
#    eprint TANGO_ERR_STACK["0"]["desc"];
    return(-1)
  }
  data  = y
  return(0)
}'

def __e2700_rfloat(unit, val) '{
  return(val / __E2700[__E2700[unit]]["scale"])
}
'

def __e2700_wfloat(unit, val) '{
  return(int(val * __E2700[__E2700[unit]]["scale"]))
}
'


#%IU%(mne, type, unit, mod, chan)
#%MDESC%
# Called by spec
def E2700_config(mne, type, unit, mod, chan) '{
  # chan will be used for counters to designate the tag address. Leave alone.
  __e2700debug "Configuring Eurotherm", mne, type, unit, mod, chan

  global __E2700[]
  __E2700["savedir"] = BLISSADM "/local/spec/userconf/"
  ushort array data[120]
  # Using unit as the serial line number, must be already defined, and chan as
  # modbus address, use E2700_ADDR to grab Modbus address.
  # E2700_ADDR must contain a string with serial line index number and the
  # modbus address separated by a blank. If there is only one string, it will be
  # the serial line index number!

  # New addition, where the connection to a Eurotherm 2700 is made through a
  # Tango Modbus ds.
  # Here we need to detect, whether the address field contains a serial index
  # number or a device domain name. Sorry, no taco device here, it is always
  # assued to have a tango ds. Simply check the lenght to be bigger than 2!

  # Warning: the modbus device address is hidden as property in the tango ds.
  # Try to work out how to change it from Spec.
  if (mne == "..") {
    local x, aux[], modbaddr
    if ((x = split(E2700_ADDR, aux)) == 2) {
      modbaddr  = aux[1] # only used with direct serial line, tango ds has a
                         # property for that.
      E2700_ADDR = aux[0]
    } else if (x == 1) {
      modbaddr = 1
    }
    if (length(E2700_ADDR) > 2) { # modbus tango ds !
      if (tango_io(E2700_ADDR, "State") == -1) {
        eprint "Eurotherm: can`t reach", E2700_ADDR "!!!"
        return ".error."
      }
      __E2700[unit] = E2700_ADDR
      # now check if is a naked Modbus or a eurotherm ds.
      # use the status message here, as the tango_get-Temperature will yield an
      # error message with the naked modbus server.
      local statmess
      statmess = tango_get(E2700_ADDR, "Status")
      if (index(statmess, "Modbus node address")) {
        # naked modbus server
        __E2700[__E2700[unit]]["type"] = 1 # which put/get function to use
      }
      else if (index(statmess, "Connected to device")) {
        __E2700[__E2700[unit]]["type"] = 2 # use tango_get Temperature to read PV
      }
      else if (index(statmess, "Unable to read temperature")) {
        # sometime yields this message, although delivering temp correctly.
        local tempx
        tempx = tango_get(E2700_ADDR, "Temperature")
        temps += 0
        if (tempx == 0) {
          eprint "Using", E2700_ADDR, "as naked Modbus server"
          __E2700[__E2700[unit]]["type"] = 1 # DONOT use tango_get Temperature to read PV
        } else {
          __E2700[__E2700[unit]]["type"] = 2 # use tango_get to read PV
        }
      }
    } else {                # assume a Spec serial line.
      need modbus-rtu       # load the necessary modbus rtu macros
      local name
      name = unit ":" modbaddr
      if ((__E2700[unit] = modb_rtu_addnode(E2700_ADDR, modbaddr, name)) < 0) {
        tty_cntl("updated?"); tty_cntl("ce"); printf("\r")
        return ".error."
      }
      __E2700[__E2700[unit]]["type"] = 0
    }
    delete __E2700[__E2700[unit]]["max_progs"]
    if (__e2700_get(unit, 122, 1, data)) {
      return ".error."
    }
    local ident
    ident = data[0]
    # ident now contains a number in hex format which will identify your
    # controller.
    # mine was 0x2480 :-)
    #  Controller identifier
    #    in format >ABCD (hex),
    #    A = 2 (series 2000) B = Range number  C = Size       D = Type
    #                          2: 2200           3: 1/32 din    0: PID/on-off
    #                          4: 2700           6: 1/16 din    2: VP
    #                                            8: 1/8 din
    #                                            4: ¼ din
    if(((ident >> 12) != 2) && (((data[0]>>8 & 0x0f) == 4) || ((data[0]>>8 & \
      0x0f) == 2))) {
      print "This is not an Eurotherm 2000 series."
      print "Check you connected the right controller "
      return ".error."
    }
    __E2700[__E2700[unit]]["ident"] = sprintf("%x00", ident >> 8)
    # There is the possibility to config the 2700 series with floating point
    # or integer values. Tag address 525 tells you how many digits appear
    # after the radix character. BUT !!!!! there was one controller with
    # firmware version 0x0411, on which this cell`s contents has no influence
    # on the modbus readings. Exclude it. The other versions at hand are the
    # 0x0461, so between the two, we`ll act as the more recent version.
    if (__e2700_get(unit, 107, 1, data)) {
      return ".error."
    }
    __E2700[__E2700[unit]]["firmwareversion"] = data[0]
    if (__e2700_get(unit, 5076, 1, data)) {
      return ".error."
    }
    __E2700[__E2700[unit]]["scale"] = pow(10, data[0])
    # catch the number of definable programs. Possible are 1, 4 and 20.
    # reading 20 segments in one go is not possible, as the modbus protocol
    # allows only 512 byte in one read. To avoid splitting this into two
    # read cycles, we only read 16 segments. This sounds sufficient.
    if (__e2700_get(unit, 89, 1, data)) { # to be verified!!!
      return ".error."
    }
    if (data[0] == 32768) { # not controlling a heater or cooler ?!?
      ;
    } else {
      __E2700[__E2700[unit]]["max_segments"] = x > 16 ? 16 : data[0]
      # set to auto mode
      if (__e2700_put(unit, 273, 0)) {
        return ".error."
      }
    }
    if (__e2700_get(unit, 5786, 1, data)) {
      return ".error."
    }
    __E2700[__E2700[unit]]["max_progs"] = data[0]
  }
  else {
    if (type == "mot") {
      if (__E2700[__E2700[unit]]["max_segments"] == 0) {
        return ".error."
      }
#      __E2700[__E2700[unit]]["dead_band"] = motor_par(mne, "dc_dead_band")
#      __E2700[__E2700[unit]]["dead_band"] = __E2700[__E2700[unit]]["dead_band"] ? \
#            __E2700[__E2700[unit]]["dead_band"] : 1
      # now read the SP
      if (__e2700_get(unit, 2, 1, data)) {
        return ".error."
      }
      __E2700[__E2700[unit]]["setpoint"] = __e2700_rfloat(unit, data[0])
      motor_par(mne, "ismotor", 1, "add")
      return(0)
    }
  }
}
'

#%IU%
#%MDESC%
# Called by spec
def E2700_cmd(mne, cmd, p1, p2, p3) '{
  local unit, module, channel, x
  ushort array data[120]
  __e2700debug "Command Eurotherm", mne, cmd, p1, p2, p3
  if (mne != "..") {
    if (cmd == "start_one") {
      # start counter, when it is one
      if (p2 == 0) {
        return
      }
      # must be a motor then
      unit = motor_par(mne, "unit")
      channel = motor_par(mne, "channel")
      local ieeevals
      if (channel > 900) {
        channel  -= 900
        ieeevals  = 1
      }
      # make a move
      # check status, only answer if status is RESET
      if (__e2700_get(unit, 5844, 1, data)) {
        eprint "Communication error with Eurotherm"
        return ".error."
      }
      if (data[0] != 1) {
        eprint "This command is not allowed! Try to set the Eurotherm to RESET!"
        return ".error."
      }
      # this is address 2, Target setpoint
      if (__e2700_put(unit, 2, __e2700_wfloat(unit, p1))) {
        return ".error."
      }
      __E2700[__E2700[unit]]["setpoint"]  = p1
    }
    else if (cmd == "position") {
      unit    = motor_par(mne, "unit")
      channel = motor_par(mne, "channel")
      local ieeevals
      if (channel > 900) {
        channel  -= 900
        ieeevals  = 1
      }
      if (channel = 2) {
        channel = 5 # use working setpoint rather than setpoint
      }
      # get angle
      # please consult manual to find out about Eurotherms way to offer
      # floating point values. Basically they are at addresses 0x8000 + original
      # address * 2
      local retval
      if (ieeevals) {
        ushort array bla[2]
        local y
        if (__e2700_get(unit, 0x8000 + 2*channel, 2, data)) {
          return(".error.")
        }
        # take 32 bit just read and turns into IEEE float
        y = __euro__float(data)
        retval = y
      }
      else {
        if (__e2700_get(unit, channel, 1, data)) {
          return ".error."
        }
        retval = __e2700_rfloat(unit, data[0])
      }
      # return position
      #
      return(retval)
    }
    else if (cmd == "get_status") {
        # this get_status is targeted only to the SP and working SP. Any other channel
        # will get the not-busy right from the start.
        unit    = motor_par(mne, "unit")
        channel = motor_par(mne, "channel")
        if (!__E2700[unit][channel]) {
            return 0
        }
        if (channel > 900) {
            channel  -= 900
        }
        if (channel != 2) {
            return(0)
        }
        # compare with where to go
        local wsp, sp
        wsp = sp = 0
        if (__e2700_get(unit, 5, 1, data)) {
            return ".error."
        }
        wsp = __e2700_rfloat(unit, data[0])
        if (__e2700_get(unit, 2, 1, data)) {
            return ".error."
        }
        sp = __e2700_rfloat(unit, data[0])
        # now this might seem risky, to compare to "=", but as the modbus protocol only
        # delivers shorts, which might then be treated with the [rw]float function, this
        # should not be a problem.
        if (wsp != sp) {
            return 2
        }
        return(0)
    }
    else if (cmd == "counts") { # this is for the counter
        # a bit dirty, but why not, address 1 delivers the PV (process value),
        # address 2 the SP (set point) and address 3 the OP (Output power).
        # and for that matter, it would allow to set any tag address as a counter.
        # just set a sufficient number of channels in the E2700 counter
        # controller. there seems, however, to be a limit of 999 in Spec
        unit    = counter_par(mne, "unit")
        channel = counter_par(mne, "channel")
        # for modbus addresses above 999, use an extra optional counter
        # parameter named "address"
        local addr
        local ieeevals
        addr = counter_par(mne, "mbaddress")
        if(addr) {
            channel  = addr
            ieeevals = counter_par(mne, "floatvalue")
        } else {
            if (channel > 900) {
                channel  -= 900
                ieeevals  = 1
            }
            if (channel + 900 * ieeevals == 998) {
                channel  = 1025
                ieeevals  = 1
            }
      }
        # please consult manual to find out about Eurotherms way to offer
        # floating point values. Basically they are at addresses 0x8000 +
        # original address * 2, answer is two shorts
        local retval
        if (ieeevals) {
            # Temperature reading with the Eurotherm2700 tango server
            if((__E2700[__E2700[unit]]["type"] == 2) && (channel == 1)) {
                return(tango_get(__E2700[unit], "Temperature"))
            }
            # and the rest is for Modbus servers and serial line comm.
            local y
            if (__e2700_get(unit, 0x8000 + 2*channel, 2, data)) {
                return ".error."
            }
            y = __euro__float(data) # takes 32 bit just read and returns IEEE float
            retval = y
        }
        else {
            if (__e2700_get(unit, channel, 1, data)) {
                return ".error."
            }
        retval = __e2700_rfloat(unit, data[0])
        }
        # return count
        #
        return(retval)
        # end of count, this will read the value and return it!
    }
  }
} '

#%IU%
#%MDESC%
# Called by spec with different keys to handle motor_par(mot,"key",new_value)
# and counter_par(cnt,"key") actions.
def E2700_par(num, key, action, p1, p2) '{
    local ismotor
    local unit
    local channel

    # Guess if called for a counter or a motor
    if(num < MOTORS) {
        ismotor = motor_par(num, "ismotor")
    } else {
        ismotor = 0
    }

    # Called for a counter
    if(!ismotor) {
       if ((key == "?") && (action == "get")) {
           return("setvalue")
       }
       if(key == "setvalue") {
           unit    = counter_par(num, "unit")
           if(!(channel = counter_par(num, "mbaddress"))) {
               channel = counter_par(num, "channel")
           }

           if(action == "set") {
               if(__e2700_put(unit, channel, __e2700_wfloat(unit, p1))) {
                   return ".error."
               }
           }
           # therefore, return 0 if write went fine
           return(E2700_cmd(num, "counts"))
       }

       # WARNING: do not use a simple return otherwise
       # the caller of the counter_par() will get value 1
       return ""
    }

    # Called for a motor
    if ((key == "?") && (action == "get")) {
        return("waitformv, ramprate")
    }
    unit    = motor_par(num, "unit")
    channel = motor_par(num, "channel")
    if (key == "waitformv") {
        if (action == "get") {
            return __E2700[unit][channel]
        }
        else {
            __E2700[unit][channel] = p1
        }
    }
    else if (key == "ramprate") {
        local ushort array data[120]
        if (action == "get") {
            if (__e2700_get(unit, 35, 1, data)) {
                return ".error."
            }
            return data[0]
        }
        else {
            if (__e2700_put(unit, 35, p1)) {
                return ".error."
            }
        }
    }

}
'

#%IU% (<data>)
#%MDESC%
#  Converts 4 byte size values into IEEE floating point value and returns it.
#%BR%
#  The 2408 can be put into a floating point mode (read cell 12275) which then
#  displays values with variable number of floating point numbers. To avoid
#  getting in trouble with that, the protocol allows to read 4 bytes from
#  adresses over 0x8000. Thus, the cell 2 reads from %BR%
#  2 x 2 + 8000h = 8004h = 32772 decimal. %BR%
def __euro__float(inarr) '{
  # so s should contain 32 bits! but I have 2 shorts
  local i j retval, s[]
  __e2700debug "Eurotherm IEEE float conversion"
  local   f1, x, e
# this would be with 4 bytes, independant of the array type
  s[0] = inarr[0] >> 8 & 0xff; s[1] = inarr[0] & 0xff
  s[2] = inarr[1] >> 8 & 0xff; s[3] = inarr[1] & 0xff
  f1 = 0x800000 | (s[1]&0x7f) << 16 | s[2] << 8 | s[3]
  e = ((s[0]&0x7F)<<1) | ((s[1]&0x80)>>7)
  e -= 127
  x = f1 * pow(2., -23. + e)
  if (s[0]&0x80)
    x = -x
# and this is with 2 16 bit values, independant of the array type
#  s = inarr
#  f1 = 0x800000 | (s[0]&0x7f) << 16 | s[1] & 0xff00 | s[1] & 0xff
#  e = ((s[0] >> 8 &0x7F)<<1) | ((s[1]>>8&0x80)>>7)
#  e -= 127
#  x = f1 * pow(2., -23. + e)
#  if (s[0]>>8&0x80)
#    x = -x
  __e2700debug sprintf("converting %04x and %04x to %g\n", inarr[0]&0xffff, inarr[1]&0xffff, x)
  return(x)
} '


#%UU% [unit]
#%MDESC%
# Gives information about the status of the controller. Not to confuse with
# europrogstatus, which gives information about a program execution in the
# controller. This one reads the status bytes on a controller and puts them out
# in human readable form. Saves you to rtfm.
# The bits of the fast status read (tag 75) are as follows:%BR%
#%PRE%
# 0 Alarm 1 State ( 0 = Safe, 1 = Alarm )%BR%
# 1 Alarm 2 State ( 0 = Safe, 1 = Alarm )%BR%
# 2 Alarm 3 State ( 0 = Safe, 1 = Alarm )%BR%
# 3 Alarm 4 State ( 0 = Safe, 1 = Alarm )%BR%
# 4 Manual Mode ( 0 = Auto, 1 = Manual )%BR%
# 5 Sensor Break ( 0 = Good PV, 1 = Sensor Broken )%BR%
# 6 Loop Break ( 0 = Good closed loop, 1 = Open Loop )%BR%
# 7 Heater Fail ( 0 = No Fault, 1 = Load fault detected )%BR%
# 8 Tune Active ( 0 = Auto Tune disabled, 1 = Auto Tune active)%BR%
# 9 Ramp/Program Complete ( 0 = Running/Reset, 1 = Complete )%BR%
# 10 PV out of range ( 0 = PV within table range, 1 = PV out of table range )%BR%
# 11 DC control module fault (0= Good,. 1= BAD)%BR%
# 12 Programmer Segment Synchronise (0= Waiting, 1 = Running)%BR%
# 13 Remote input sensor break (0 = Good, 1 = Bad)%BR%
#%PRE%
# After reflexion, Ill give you all that is there :-), 74  to 77
def euro2700status '{
  eprint "The 2700s of Eurotherm offer some diagnostics. Here are the 4 bytes"
  eprint "one can look at with their bit`s meaning."
  local unit, x
  ushort array data[4]
  if ($#) unit = $1
  if (__e2700_get(unit, 74, 4, data)) {
    return(-1)
  }
  # tag address 74: Fast Status Byte
  x = data[0]
  if (x & 0x01)   eprint "Alarm 1 State"
  if (x & 0x02)   eprint "Alarm 2 State"
  if (x & 0x04)   eprint "Alarm 3 State"
  if (x & 0x08)   eprint "Alarm 4 State"
  if (x & 0x10)   eprint "Manual Mode"
  else            eprint "Auto Mode"
  if (x & 0x20)   eprint "Sensor Broken"
  else            eprint "Good PV"
  if (x & 0x40)   eprint "Open Loop"
  else            eprint "Good closed loop"
  if (x & 0x80)   eprint "Heater Fail: Load fault detected"
  else            eprint "Heater Fail: No Fault"
  # Summary Output Status Word, tag address 75
  x = data[1]
  # skip all information already present from byte 74
  eprintf("Tune Active: Auto Tune active: ")
  if (x & 0x100)  eprint "active"
  else            eprint "disabled"
  eprintf("Ramp/Program Complete: ")
  if (x & 0x200)  eprint "Complete"
  else            eprint "Running/Reset"
  if (x & 0x400)  eprintf("PV out of")
  else            eprintf("PV within")
  eprint " table range"
  eprintf("DC control module fault: ")
  if (x & 0x800)  eprint "BAD"
  else            eprint "Good"
  eprintf("Programmer Segment Synchronise: ")
  if (x & 0x1000) eprint "Running"
  else            eprint "Waiting"
  eprintf("Remote input sensor break: ")
  if (x & 0x2000) eprint "Bad"
  else            eprint "Good"
  # tag address 76: Control Status Word
  x = data[2]
  if (x & 0x0001) eprint "Control algorithm Freeze"
  if (x & 0x0002) eprint "PV input sensor broken"
  if (x & 0x0004) eprint "PV out of sensor range"
  if (x & 0x0008) eprint "Self Tune failed"
  if (x & 0x0010) eprint "PID servo signal"
  if (x & 0x0020) eprint "PID debump signal"
  if (x & 0x0040) eprint "Fault detected in closed loop behaviour (loop break)"
  if (x & 0x0080) eprint "Freezes the integral accumulator"
  if (x & 0x0100) eprint "Indicates that a tune has completed successfully"
  if (x & 0x0200) eprint "Direct/reverse acting control"
  if (x & 0x0400) eprint "Algorithm Initialisation flag"
  if (x & 0x0800) eprint "PID demand has been limited."
  if (x & 0x1000) eprint "Autotune enabled"
  if (x & 0x2000) eprint "Adaptive tune enabled"
  if (x & 0x4000) eprint "Automatic Droop compensation enabled"
  if (x & 0x8000) eprint "Manual / Auto mode switch"
  # tag address 77: Instrument Status Word
  x = data[3]
  if (x & 0x0001) eprint "Config/Oper mode switch"
  if (x & 0x0002) eprint "Disables limit checking"
  if (x & 0x0004) eprint "SRL ramp running (Read Only)"
  if (x & 0x0008) eprint "Remote setpoint active"
  if (x & 0x0010) eprint "Alarm acknowledge switch."
}'


def __e2700_simple '{
  # only for holger
  # $1 address
  # $2  # of bytes
  # $3 unit
  local numb
  numb = $2 ? $2 : 1
  __e2700_get($3, $1, numb, data)
  numb--
  print data[0:numb]
}
'

#%UU% unit
#%MDESC%
# Show the PID values for unit
#%BR%
def euro2700showpid '{
  local prop, inte, deri
  __e2700_get(unit, 6, 1, data)
  prop = data[0]
  __e2700_get(unit, 8, 2, data)
  inte = data[0]
  deri = data[1]
  print "The PID values are : ", prop, inte, deri
}
'

#%UU%
#%MDESC%
# Please run without arguments to find out about the possible actions.
#
# Put controller into configuration mode and set one of several special cells.
# The following values influence the macros considerably. They can be set from
# the computer to make the user experience less painful.
#%BR%
# Note: no spaces are allowed around the `=` signs!
#%BR%
# Note: the decimal display will only be valid, if the Resolution is set to 0 (full)!!!
#
#%DL%
#%DT% Decimal places displayed
# (mb address 525)
#%DL%
#%DT%0:    nnnn
#%DT%1:    nnn.n
#%DT%2:    nn.nn
#%XUL%
#
#%DT% Resolution
#(mb address 12275)
#%DL%
#%DT%0:    Full
#%DT%1:    Integer
#%XUL%
#%DT% Low Range Limit
#(mb address 11)
#%DT% High Range Limit
#(mb address 12)
#%DT% Instrument Units
#(mb address 516)
#%DL%
#%DT%0:    oC
#%DT%1:    oF
#%DT%2:    oK
#%DT%3:    None
#%XUL%
#%XUL%

def euro2700configuration '{
  local currstat, _states, option, unit, x, i, good
    _states[0] = "NORMAL"; _states[1]  = "STANDBY"; _states[2] = "CONFIGURATION"
  # the variable usine refers to the French word `usine a gaz`, which characterizes an excessive
  # complexity, which remains incomprehsible for the uninitiated :-)
  local myarr[], aux[], bla[], varnam, usine[], doit
  # I just hate to have the same strings twice, so use x as first index in usine[].
  x = "Resolution";      usine[x]["address"]  = 12275; usine[x]["poss"] = "0(Full)|1(Integer)"
  x = "DecimalPlaces";   usine[x]["address"]  =  5076; usine[x]["poss"] = "0|1|2"
  x = "LowRangeLimit";   usine[x]["address"]  =    11; usine[x]["poss"] = "value"
  x = "HighRangeLimit";  usine[x]["address"]  =    12; usine[x]["poss"] = "value"
  x = "InstrumentUnits"; usine[x]["address"]  = 10369; usine[x]["poss"] = "0(C)|1(F)|2(K)|3(None)"
# those values don`t need a reset.
#  x = "Proportional";    usine[x]["address"]  =     6; usine[x]["poss"] = "value"
#  x = "Integral";        usine[x]["address"]  =     8; usine[x]["poss"] = "value"
#  x = "Derivate";        usine[x]["address"]  =     9; usine[x]["poss"] = "value"
#  x = "RampHoldBack";    usine[x]["address"]  =    70; usine[x]["poss"] = "0(off)|1(low)|2(high)|3(Band)"
#  x = "RampHoldValue";   usine[x]["address"]  =    65; usine[x]["poss"] = "value"
#  x = "RampRate";        usine[x]["address"]  =    35; usine[x]["poss"] = "value"
#  x = "RampRateUnits";   usine[x]["address"]  =   531; usine[x]["poss"] = "0(Sec)|1(Min)|2(Hour)"
#  x = "TimeUnit";        usine[x]["address"]  =   529; usine[x]["poss"] = "value"
  doit = 0 # any action necessary ?
  if(!$#){
    local x, y[], z, oldz
    printf("usage: %s ", "$0")
    for (x in usine) {
      split(x, y, "\034")
      z = y[0]
      if (z != oldz) {
        if (usine[z]["address"]) {
          print "\t" z "=" usine[z]["poss"], " \\ "
        }
        oldz = z
      }
    }
    exit
  }
  split("$*", myarr)
  for (option in myarr) {
    split(myarr[option], aux, "=")
    varnam = aux[0]
    usine[varnam]["newval"] = aux[1]
    @varnam = aux[1]
    bla[varnam] = "" # as a placeholder for the next for()
  }
  # Now do some checking.
  # Note: the decimal display will only be valid, if the Resolution is set to 0 (full)!!!
  # do this any time DecimalDisplay is changed, the overhead is minimal
  if (("DecimalDisplay" in bla) && !("Resolution" in bla)) {
    bla["Resolution"] = "" # make sure Resolution is set with the DecimalDisplay
    usine["Resolution"]["newval"] = 0
  }
  # the following checks if anything at all needs doing
  for (x in bla) {
    if (usine[x]["address"]) {
      if (__e2700_get(unit, usine[x]["address"], 1, data)) {
        eprint "Can`t access unit", unit, "!"
        exit
      }
      usine[x]["value"] = data[0]
      if (usine[x]["value"] != usine[x]["newval"]) {
        doit = 1 # yes, we need to go into config mode.
        usine[x]["doit"] = 1 # and change this value
      }
    }
  }
  if (doit) {
    # put controller into config mode
    if (__e2700_get(unit, 199, 1, data)) {
      eprint "Can`t access unit", unit, "!"
      exit
    }
    currstat = data[0]
    print "The controller was in", _states[currstat], "mode."
    if (__e2700_put(unit, 199, 2)) {
      eprint "Can`t access unit", unit, "!"
      exit
    }
    for (x in bla) {
      if (x == "unit") {
        continue
      }
      if (usine[x]["doit"]) {
        if (__e2700_put(unit, usine[x]["address"], usine[x]["newval"])) {
          eprint "Can`t access unit", unit, "!"
          exit
        }
        if (__e2700_get(unit, usine[x]["address"], 1, data)) {
          eprint "Can`t access unit", unit, "!"
          exit
        }
        print x "`s old value was", usine[x]["value"] ", the new value is", data[0]
      }
    }
    # set controller back into normal mode
    if (__e2700_get(unit, 199, 1, data)) {
      eprint "Can`t access unit", unit, "!"
      exit
    }
    currstat = data[0]
    if (__e2700_put(unit, 199, 0)) {
      eprint "Can`t access unit", unit, "!"
      exit
    }
    # here the controller is going to go offline for a time.
    print
    print "The controller will go through a reset now, this will take less than a minute!"
    print "Please wait until the controller becomes responsive."
    good = 0
    for (i = 0; i < 60; i ++) {
      sleep(5)
      if (__e2700_get(unit, 199, 1, data) == 0) {
        good = 1
        break
      } else {
        tty_cntl("updated?"); tty_cntl("ce"); printf("\r")
      }
    }
    if (! good && __E2700[__E2700[unit]]["type"]) {
      print "Apparently there is a problem with the communication with the controller."
      print "Please restart the device server!"
    } else if (! good) {
      # I found that when using the modbus-rtu macros a good bash at the serial line
      # will solve the problem. Usually a ct 1 will get it back to work.
      COUNT_TIME = 1
      count_em
    } else {
      if (__e2700_get(unit, 199, 1, data)) {
        eprint "Can`t access unit", unit, "!"
        exit
      }
      currstat = data[0]
      print "Now the controller is in", _states[currstat], "mode!"
    }
    print "After these changes, it is probable you have to reconfigure."
    reconfig
  }
  else {
    print  "No action necessary!"
  }
}
'

#%UU%
#%MDESC%
# Please run without arguments to find out about the possible actions.
#
# Set one of several special cells.
# The following values influence the macros considerably. They can be set from
# the computer to make the user experience less painful.
#%BR%
# Note: no spaces are allowed around the `=` signs!
#%BR%

def euro2700parameters '{
  local currstat, _states, option, unit, x, i, good
  # the variable usine refers to the French word `usine a gaz`, which characterizes an excessive
  # complexity, which remains incomprehsible for the uninitiated :-)
  local myarr[], aux[], bla[], varnam, usine[], doit
  # I just hate to have the same strings twice, so use x as first index in usine[].
#  x = "Proportional";    usine[x]["address"]  =     6; usine[x]["poss"] = "value"
#  x = "Integral";        usine[x]["address"]  =     8; usine[x]["poss"] = "value"
#  x = "Derivate";        usine[x]["address"]  =     9; usine[x]["poss"] = "value"
  x = "unit"
  x = "RampHoldBack";    usine[x]["address"]  =    70; usine[x]["poss"] = "0(off)|1(low)|2(high)|3(Band)"
  x = "RampHoldValue";   usine[x]["address"]  =    65; usine[x]["poss"] = "value"
  x = "RampRate";        usine[x]["address"]  =    35; usine[x]["poss"] = "value"
  x = "RampRateUnits";   usine[x]["address"]  =   531; usine[x]["poss"] = "0(Sec)|1(Min)|2(Hour)"
    split("$*", myarr)
    for (option in myarr) {
        split(myarr[option], aux, "=")
        varnam = aux[0]
        usine[varnam]["newval"] = aux[1]
        @varnam = aux[1]
        bla[varnam] = "" # as a placeholder for the next for()
    }
  doit = 0 # any action necessary ?
  if((!$#) || (($# == 1) && unit)) {
    local x, y[], z, oldz, str, len
    ushort array data[120]
    printf("usage: %s \\\n", "$0")
    for (x in usine) {
      split(x, y, "\034")
      z = y[0]
      if (z == "unit") {
        print "\tunit=[1|2|..] \\ "
        continue
      }
      if ( ! __E2700[unit]) {
        local i, xstr
        eprint "No controller with unit number", unit
        for (i=0; i < 50; i++) {
            if (__E2700[i]) xstr = xstr " " i
        }
        eprint "Please use one of:" xstr
        exit
      }
      if (z != oldz) {
        if (usine[z]["address"]) {
          str = sprintf("\t%s=%s \\ ", z, usine[z]["poss"])
        }
        len = length(str)
        for(i = len; i < 50; i++) {
          str = str " "
        }
        if (__e2700_get(unit, usine[z]["address"], 1, data)) {
          eprint "Can`t access unit", unit, "!"
          exit
        }
        str = str "(current value: " data[0] ")"
        print str
        oldz = z
      }
    }
  }
  split("$*", myarr)
  for (option in myarr) {
    split(myarr[option], aux, "=")
    varnam = aux[0]
    usine[varnam]["newval"] = aux[1]
    @varnam = aux[1]
    bla[varnam] = "" # as a placeholder for the next for()
  }
  # the following checks if anything at all needs doing
  for (x in bla) {
    if (usine[x]["address"]) {
      if (__e2700_get(unit, usine[x]["address"], 1, data)) {
        eprint "Can`t access unit", unit, "!"
        exit
      }
      usine[x]["value"] = data[0]
      if (usine[x]["value"] != usine[x]["newval"]) {
        doit = 1 # yes, we need to go into config mode.
        usine[x]["doit"] = 1 # and change this value
      }
    }
  }
  if (doit) {
    for (x in bla) {
      if (x == "unit") {
        continue
      }
      if (usine[x]["doit"]) {
        if (__e2700_put(unit, usine[x]["address"], usine[x]["newval"])) {
          eprint "Can`t access unit", unit, "!"
          exit
        }
        if (__e2700_get(unit, usine[x]["address"], 1, data)) {
          eprint "Can`t access unit", unit, "!"
          exit
        }
        print x "`s old value was", usine[x]["value"] ", the new value is", data[0]
      }
    }
  }
  else {
    print  "No action necessary!"
  }
}
'

#%UU% mne
#%MDESC%
# toggle behaviour for motor mne to wait for the end of a move, if a simple mv
# command is using a ramp rate.
#%END%
# call this macro euro... instead of euro2400 as I will try to make macros for
# both types 2400s and 2700s.
def eurowaitformove '{
    if ($# != 1 ) {
        print "Usage: $0 motor_num"
        exit
    }
    local num, unit, channel, x
    if ((num = motor_num("$1")) == -1) {
        eprint "No such motor", "$1"
        exit
    }
    unit    = motor_par(num, "unit")
    channel = motor_par(num, "channel")
    x = motor_par(num, "waitformv")
    motor_par(num, "waitformv", x ? 0 : 1)
    print "Motor", motor_mne(num), "will", motor_par(num, "waitformv") ? "NOT " : \
        "" "go back to the prompt after a mv command"
}
'




#%MACROS%
#%IMACROS%
#%INTERNALS%
#%AUTHOR% H. Witsch, BLISS - ESRF, derived from the macros in eurotherm2400.mac,
# mainly programmed by H. Witsch
# Revision: preliminary, Date: 2009/06/16
#%TOC%