esrf

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

#%TITLE% MODBUS-RTU.MAC
#%NAME%
#  Macro functions to access slave MODBUS devices in RTU mode, which uses 
#binary communication as opposed to ASCII in the file modbus.mac.
#
#%DESCRIPTION%
#  This macro set provides functions to access remote devices using MODBUS
#  protocol over serial lines using the binary (called RTU) mode.%BR%
#  %BR%
#  The following MODBUS functions are implemented and have been tested with a
#  Eurotherm 2408 temperature controller. Further functions will have to be added as
#  new instruments come along:
#  %UL%%UL%
#  %LI%  Function code | Function
#  %LI%  01 or 02\0\0\0\0\0\0| Read n bits
#  %LI%  03 or 04\0\0\0\0\0\0| Read n words
#  %LI%  05\0\0\0\0\0\0\0\0\0\0\0\0| Write a bit
#  %LI%  06\0\0\0\0\0\0\0\0\0\0\0\0| Write a word
#  %LI%  07\0\0\0\0\0\0\0\0\0\0\0\0| Fast Read of Status
#  %LI%  08\0\0\0\0\0\0\0\0\0\0\0\0| Loopback
#  %LI%  16\0\0\0\0\0\0\0\0\0\0\0\0| Write n words
#  %XUL%%XUL%
#  The remote devices must behave as MODBUS slave nodes and have to be
#  register with the %B%modb_rtu_addnode()%B% macro function before being
#  accessed. This function returns a node number that must be used as the
#  first parameter of the all the macro functions used to exchange data with
#  the remote devices.%BR%
#  Many of the input/output functions use an array to transfer data. This
#  array can be a data array or an associative array. In the case of using
#  a data array, the user is responsible of providing an array of the correct
#  size to hold the data.%BR%
#  %BR%
#  When an error happens during the execution of a MODBUS function, the 
#  corresponding error is stored in the global variable MODB_ERR if it 
#  exists and the macro function returns the negative value -MODB_ERR. An error
#  message is printed on the output terminal unless MODB_ERR is previously set
#  to -1. If the function completes succesfully, MODB_ERR is set to 0.%BR%
#  %BR%
#  The error codes are the following:
#  %UL%%UL%
#  %LI%  1001 - Bad device or interface
#  %LI%  1002 - Bad address
#  %LI%  1003 - Bad data parameters
#  %LI%  1004 - Not a registered slave node
#  %LI%  1005 - No answer from slave node
#  %LI%  1006 - Bad answer from slave node
#  %LI%  1007 - Function mismatch
#%EXAMPLE%
#
#%B%myslave\0\0\0=\0modb_rtu_addnode(3,\047)%B%%BR%
#%B%modb_rtu_read_status(myslave,\0data)%B%%BR%
#  This reads the status of the slave with
#  MODBUS address 47 that is connected to the %B%spec%B% serial
#  interface 3.%BR%
#$Revision: 1.5 $, $Date: 2009/03/11 13:06:06 $
#%END%


#%UU% (<device>, <maddr> [, <name>])
#%MDESC%
#  Tries to connect to a MODBUS slave node. If the node is found, the macro
#  function appends it to the internal list and returns a unique node number
#  that must be used to access the slave with this macro set.%BR%
#
#  The parameter <device> must be either the number of a serial line interface
#  or an ESRF device name. The parameter <maddr> is required and must
#  correspond to the MODBUS address of the node.%BR%
#
#  If there is an error, the macro returns -MODB_ERR, and the node is not
#  added to the internal list.%BR%
#
def modb_rtu_addnode(device, maddr, name) '{
  global MODBUS[]
  local comm answ data[] node

  if (maddr < 0 || maddr > 255) {
    if (MODB_ERR != -1)
      printf(" MODBUS: Bad address (%d).\n", address)
    return(-(MODB_ERR = 1002))
  }

  if (device + 0 == device) {
    comm = "serial"
    answ = ser_par(device, "timeout")
  } else if (index(device, "/") != 0) {
    comm = "esrf"
    answ = esrf_io(device, "DevState")
  }
  if (answ < 0) {
    if (MODB_ERR != -1)
      printf(" MODBUS: Bad device or interface (%s).\n", device)
    return(-(MODB_ERR = 1001))
  }
  MODBUS[0]["comm"] = comm
  MODBUS[0]["dev"] = device
  MODBUS[0]["addr"] = maddr
  answ = modb_rtu_read_diagnostics(0, 1, 0xff00, data)
  if ( answ < 0 ) {
    return(-MODB_ERR)
  }
  for (node = 1; node <= MODBUS[0]["n_nodes"]; node++) {
    if ((name != "" && MODBUS[node] == name) || \
        (MODBUS[node]["dev"] == device )) {
      if (MODBUS[node] == "") {
        printf("Replacing unamed MODBUS node by %s:%d\n", device, maddr)
        name = device ":" maddr
      } else if (MODBUS[node] != name) {
        printf("Replacing MODBUS node \'%s\' by \'%s\' at %s:%d\n", \
               MODBUS[node], name, device, maddr)
      }
      break;
    }
  }
  if (node > MODBUS[0]["n_nodes"])
    MODBUS[0]["n_nodes"] = node

  MODBUS[node] = name
  MODBUS[node]["comm"] = comm
  MODBUS[node]["dev"]  = device  
  MODBUS[node]["addr"] = maddr

  MODB_ERR = 0
  return(node)
} '

#%UU% (<device> [, <name>])
#%MDESC%
#  Tries to find a MODBUS slave node. The macro will run through the MODBUS
#  addresses starting with 1 going to 255. By the nature of things, higher
#  addresses will take longer to be found. Once the node is found, the macro
#  function appends it to the internal list and returns a unique node number
#  that must be used to access the slave with this macro set.%BR%
#
#  The parameter <device> must be either the number of a serial line interface
#  or an ESRF device name. %BR%
#
#  If there is an error, the macro returns -MODB_ERR, and the node is not
#  added to the internal list.%BR%
#
def modb_rtu_searchnode(device, name, opts) '{
  global MODBUS[]
  local comm answ data[] node maxn maddr

  if (device + 0 == device) {
    comm = "serial"
    answ = ser_par(device, "timeout")
  } else if (index(device, "/") != 0) {
    comm = "esrf"
    answ = esrf_io(device, "DevState")
  }
  if (answ < 0) {
    if (MODB_ERR != -1)
      printf(" MODBUS: Bad device or interface (%s).\n", device)
    return(-(MODB_ERR = 1001))
  }
  MODBUS[0]["comm"] = comm
  MODBUS[0]["dev"] = device
  maxn = opts ? opts : 255
  for (maddr = 1; maddr < maxn; maddr++) {
    MODBUS[0]["addr"] = maddr
    if (modb_rtu_read_diagnostics(0, 1, 0xff00, data) == 0xff00) {
      break
    } else {
      tty_cntl("updated?"); tty_cntl("ce"); printf("\r")
    }
  }
  if ( node > maxn ) {
    return(-(MODB_ERR = 1005))
  }
  for (node = 1; node <= MODBUS[0]["n_nodes"]; node++) {
    if ((name != "" && MODBUS[node] == name) || \
        (MODBUS[node]["dev"] == device )) {
      if (MODBUS[node] == "") {
        printf("Replacing unamed MODBUS node by %s:%d\n", device, maddr)
        name = device ":" maddr
      } else if (MODBUS[node] != name) {
        printf("Replacing MODBUS node \'%s\' by \'%s\' at %s:%d\n", \
               MODBUS[node], name, device, maddr)
      }
      break;
    }
  }
  if (node > MODBUS[0]["n_nodes"])
    MODBUS[0]["n_nodes"] = node

  MODBUS[node] = name
  MODBUS[node]["comm"] = comm
  MODBUS[node]["dev"]  = device  
  MODBUS[node]["addr"] = maddr

  MODB_ERR = 0
  return(node)
} '

#%UU% (<device>)
#%MDESC%
#  Tries to work out the serial line parameters to communicate with a MODBUS
#  slave node. The macro will run through the possible serial line parameters,
#  which are 9600, 19200, 4800, 2400 and 1200 baud. At each speed, it will try
#  to communicate at 8 data bits, then 7 data bits with even, then odd parity.
#  By the nature of things, things are going to be slow. Once a communication is
#  established, the macro will offer the result in printing.
#  %BR%
#  The parameter <device> must be either the number of a serial line interface
#  or an ESRF device name.
def modb_rtu_searchparameters(device, maddr, opts) '{
  global MODBUS[]
  local comm answ data[] node bla, i, speeds, aux[], databs
  local serpar[], j, k, comdev, databs

  if (device + 0 == device) {
    comm = "serial"
    answ = ser_par(device, "timeout")
  } else if (index(device, "/") != 0) {
    comm = "esrf"
    answ = esrf_io(device, "DevState")
  }
  if (answ < 0) {
    if (MODB_ERR != -1)
      printf(" MODBUS: Bad device or interface (%s).\n", device)
    return(-(MODB_ERR = 1001))
  }
  MODBUS[0]["comm"] = comm
  MODBUS[0]["dev"] = device
  MODBUS[0]["addr"] = maddr

  # in order to use an esrf_io call, we need the device_id
  if (comm == "serial") {
    comdev = ser_par(device, "device_id")
  } else {
    comdev = device
  }
  SL_NONE  = 0; SL_ODD   = 1; SL_EVEN  = 3
  local Parity[]
  Parity[SL_NONE] = ""
  Parity[SL_ODD ] = ",  odd parity"
  Parity[SL_EVEN] = ", even parity"
  SL_DATA8 = 0; SL_DATA7 = 1
  local DataBits[]
  DataBits[SL_DATA8] = 8
  DataBits[SL_DATA7] = 7
  local parmode[]
  parmode[j++] = SL_NONE
  parmode[j++] = SL_ODD
  parmode[j++] = SL_EVEN

  serpar[0]  = 4; serpar[1] = SL_NONE   # No parity
  serpar[2]  = 5; serpar[3] = SL_DATA8  # 8 data bits
  serpar[4]  = 7; serpar[5] = 9600      # baud rate

  bla = "19200 9600 4800 2400 1200"
  speeds = split(bla, aux)
  def blabla \'{
    local x, str
    if (esrf_io(comdev, "DevSerSetParameter", serpar) < 0)
      return(-(MODB_ERR = 1001))

    str = sprintf("%5d baud, %d data bits", serpar[5], serpar[3])
    str = str sprintf("%-14s", Parity[serpar[1]])
    x = modb_rtu_read_diagnostics(0, 1, 0xff00, data)
    # clear error message
    if (x < 0) {
      tty_cntl("updated?"); tty_cntl("ce"); printf("\r")
      str = str  " .... No!"
    } else {
      str = str  " .... Yes! ******"
    }
    print str
    # one could try to send a BI-SYNC command "II" here.
  } \'
  print "Serial line config: "
  for (i = 0; i < speeds; i++) {
    serpar[5] = aux[i]

    for (j = 0; j < 2; j++) {
      serpar[3] =  j
      if (j) {
        for (k = 0; k < 3; k ++) {
          serpar[1] = parmode[k]
          ESRF_ERR = -1
          blabla
        }
      } else {
          serpar[1] = SL_NONE
          blabla
      }
    }
  }
  undef blabla
} '

#%UU% (<node>, <data>)
#%MDESC%
#  This macro executes the function 7 "FAST READ OF STATUS" in a slave MODBUS node.
#  The slave node is selected by the parameter <node> that must be a valid
#  value previously returned by the macro function %B%modb_rtu_addnode()%B%.
#  The 8-bit status is returned in the first element of the array <data>.%BR%
#  If an error happens, the macro returns -MODB_ERR (see error codes),
#  otherwise 1.%BR%
#
def modb_rtu_read_status(node, data, opts) '{
   local err
   modb_rtu__debug_start 
   if (!(opts & 0x02)) {
      if (err = modb_rtu__frame(node, 7))
         return(err)
      if (err = modb_rtu__put(node))
         return(err)
   }
   if (!(opts & 0x01)) {
      if (err = modb_rtu__get(node, 7))
         return(err)
      data[0] = MODBFRAME[2]
      MODB_ERR = 0
      modb_rtu__debug "end"
      return(MODBFRAME[2])
   } 
   MODB_ERR = 0
   modb_rtu__debug  "end"
   return(0)
}'

#%UU% (<node>, <subcode>, <dfield>, <data>)
#%MDESC%
#  This macro executes the function 8 "DIAGNOSTIC LOOPBACK" in a
#  slave MODBUS node.
#  The slave node is selected by the parameter <node> that must be a valid
#  value previously returned by the macro function %B%modb_rtu_addnode()%B%.

#  If no error happens, the macro function returns the number of words actually
#  read from the slave and stored in the first positions of the array <data>.
#  Each valid position of <data> contains a 16-bit word.
#  If an error happens, the macro returns -MODB_ERR (see error codes).%BR%
#
def modb_rtu_read_diagnostics(node, subcode, dfield, data, opts) '{
   modb_rtu__debug_start 
   return(modb_rtu__read(node, subcode, dfield, data, opts, 8))
}'

##%UU% (<node>, <function>, <data>)
##%MDESC%
##  This macro executes a function of the user's choice in a slave MODBUS node.
#
##  The slave node is selected by the parameter <node> that must be a valid
##  value previously returned by the macro function %B%modb_rtu_addnode()%B%.
#
##  If an error happens, the macro returns -MODB_ERR (see error codes).%BR%
##
#def modb_rtu_function(node, function, opts) '{
#   local err
#   modb_rtu__debug_start 
#   if (!(opts & 0x02)) {
#      if (err = modb_rtu__frame(node, 17))
#         return(err)
#      if (err = modb_rtu__put(node))
#         return(err)
#   }
#   if (!(opts & 0x01)) {
#      if (err = modb_rtu__get(node, 17))
#         return(err)
#      MODB_ERR = 0
#      modb_rtu__debug "end"
#      return(MODBFRAME[2])
#   } 
#   MODB_ERR = 0
#   modb_rtu__debug  "end"
#   return(0)
#}'

#%UU% (<node>, <address of first bit>, <number of bits>, <data>)
#%MDESC%
#  This macro executes the function 1 or 2 "Read n bits" in a slave MODBUS node.

#  The slave node is selected by the parameter <node> that must be a valid
#  value previously returned by the macro function %B%modb_rtu_addnode()%B%.

#  If no error happens, the macro function returns the number of bits actually
#  read from the slave and stored in the first positions of the array <data>.
#  Each valid position of <data> contains ONE bit of the answer.  
#  If an error happens, the macro returns -MODB_ERR (see error codes).%BR%
#
def modb_rtu_read_n_bits(node, fbaddr, nbits, data, opts) '{
   local err, funct
   funct = 1
   modb_rtu__debug_start
   modb_rtu__debug "modb_rtu_read_n_bits"
   if (err = modb_rtu__checkpar(nbits, data))
      return(err)
   if (!(opts & 0x02)) {
      if (err = modb_rtu__frame(node, funct, fbaddr, nbits, data))
         return(err)
      if (err = modb_rtu__put(node))
         return(err)
   }
   if (!(opts & 0x01)) {
      if (err = modb_rtu__get(node, funct))
         return(err)
      modb_rtu__data(data, nbits)
      MODB_ERR = 0
      modb_rtu__debug "end"
      return(nbits)
   }
   MODB_ERR = 0
   modb_rtu__debug "end"
   return(0)
} '

#%UU% (<node>, <address of bit>, <value of bit>, <data>)
#%MDESC%
#  This macro executes the function 5 "write a bit" in a slave MODBUS node.

#  The slave node is selected by the parameter <node> that must be a valid
#  value previously returned by the macro function %B%modb_rtu_addnode()%B%.

#  If no error happens, the macro function returns 1
#  If an error happens, the macro returns -MODB_ERR (see error codes).%BR%
#
def modb_rtu_write_a_bit(node, fbaddr, value, data, opts) '{
   local err, funct
   funct = 5
   modb_rtu__debug_start
   modb_rtu__debug "modb_rtu_write_a_bit"
   if (!(opts & 0x02)) {
      if (err = modb_rtu__frame(node, funct, fbaddr, value, data))
         return(err)
      if (err = modb_rtu__put(node))
         return(err)
   }
   if (!(opts & 0x01)) {
      if (err = modb_rtu__get(node, funct))
         return(err)
      modb_rtu__data(data, value)
      MODB_ERR = 0
      modb_rtu__debug "end"
      return(1)
   }
   MODB_ERR = 0
   modb_rtu__debug "end"
   return(0)
} '

#%UU% (<node>, <address of first word>, <number of words>, <data>)
#%MDESC%
#  This macro executes the function 3 or 4 "Read n words" in a slave MODBUS node.

#  The slave node is selected by the parameter <node> that must be a valid
#  value previously returned by the macro function %B%modb_rtu_addnode()%B%.

#  If no error happens, the macro function returns the number of words actually
#  read from the slave and stored in the first positions of the array <data>.
#  Each valid position of <data> contains ONE bit of the answer.  
#  If an error happens, the macro returns -MODB_ERR (see error codes).%BR%
#
def modb_rtu_read_n_words(node, fwaddr, nwords, data, opts) '{
   local err, funct
   funct = 3
   modb_rtu__debug_start
   modb_rtu__debug "modb_rtu_read_n_words"
   if (err = modb_rtu__checkpar(nwords, data))
      return(err)
   if (!(opts & 0x02)) {
      if (err = modb_rtu__frame(node, funct, fwaddr, nwords, data))
         return(err)
      if (err = modb_rtu__put(node))
         return(err)
   }
   if (!(opts & 0x01)) {
      if (err = modb_rtu__get(node, funct))
         return(err)
      modb_rtu__data(data, nwords)
      MODB_ERR = 0
      modb_rtu__debug "end"
      return(nwords)
   }
   MODB_ERR = 0
   modb_rtu__debug "end"
   return(0)
} '

#%UU% (<node>, <address of word>, <value of word>, <data>)
#%MDESC%
#  This macro executes the function 6 "Write a word" in a slave MODBUS node.

#  The slave node is selected by the parameter <node> that must be a valid
#  value previously returned by the macro function %B%modb_rtu_addnode()%B%.

#  If no error happens, the macro function returns 1.
#  Each valid position of <data> contains ONE bit of the answer.  
#  If an error happens, the macro returns -MODB_ERR (see error codes).%BR%
#
def modb_rtu_write_a_word(node, fwaddr, value, opts) '{
   local err, funct, data[]
   funct = 6
   modb_rtu__debug_start
   modb_rtu__debug "modb_rtu_write_a_word"
   if (!(opts & 0x02)) {
      if (err = modb_rtu__frame(node, funct, fwaddr, value, data))
         return(err)
      if (err = modb_rtu__put(node))
         return(err)
   }
   if (!(opts & 0x01)) {
      if (err = modb_rtu__get(node, funct))
         return(err)
#      modb_rtu__data(data, value)
      MODB_ERR = 0
      modb_rtu__debug "end"
      return(nwords)
   }
   MODB_ERR = 0
   modb_rtu__debug "end"
   return(0)
} '

#%UU% (<node>, <address of first word>, <number of words>, <data>)
#%MDESC%
#  This macro executes the function 16 "WRITE N WORDS" in a slave MODBUS node.

#  The slave node is selected by the parameter <node> that must be a valid
#  value previously returned by the macro function %B%modb_rtu_addnode()%B%.

#  If no error happens, the macro function returns the number of words actually
#  written to the slave.
#  If an error happens, the macro returns -MODB_ERR (see error codes).%BR%
#
def modb_rtu_write_n_words(node, fwaddr, nwords, data, opts) '{
   local err, funct
   funct = 16
   modb_rtu__debug_start
   modb_rtu__debug "modb_rtu_write_n_words"
   if (err = modb_rtu__checkpar(nwords, data))
      return(err)
   if (!(opts & 0x02)) {
      if (err = modb_rtu__frame(node, funct, fwaddr, nwords, data))
         return(err)
      if (err = modb_rtu__put(node))
         return(err)
   }
   if (!(opts & 0x01)) {
      if (err = modb_rtu__get(node, funct))
         return(err)
      modb_rtu__data(data, nwords)
      MODB_ERR = 0
      modb_rtu__debug "end"
      return(nwords)
   }
   MODB_ERR = 0
   modb_rtu__debug "end"
   return(0)
} '

#%IU% (<node>, <daddr>, <nitems>, <data>, <function>)
#%MDESC%
#  Executes any read data function in a slave MODBUS node.%BR%
#
def modb_rtu__read(node, daddr, nitems, data, opts, funct) '{
   local err

   modb_rtu__debug "modb_rtu__read"
   if (err = modb_rtu__checkpar(nitems, data)) 
      return(err)
   if (!(opts & 0x02)) {
      if (err = modb_rtu__frame(node, funct, daddr, nitems))
         return(err)
      if (err = modb_rtu__put(node))
         return(err)
   }
   if (!(opts & 0x01)) {
      if (err = modb_rtu__get(node, funct))
         return(err)
      modb_rtu__data(data, nitems)
      MODB_ERR = 0
      modb_rtu__debug "end"
      return(nitems)
   }
   MODB_ERR = 0
   modb_rtu__debug "end"
   return(0)
}'

#%IU% (<data>, <nitems>)
#%MDESC%
#  Extracts <nitems> from the binary frame in MODBFRAME and stores them
#  as %B%float%B% %B%values%B% in the array <data>.%BR%Tested only for function 
#  modb_rtu_read_n_words(). %BR%
#  The 2408 can be put into a floating point mode (read cell 12550) 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 modb_rtu__float(data, nitems) '{
  local funct i j aux
  local s0 s1 s2 s3

  modb_rtu__debug "modb_rtu__float"
  funct = MODBFRAME[1]
  j = 3 # where the answer starts
  if (funct == 3) {
    local   f1, x, e, s[]

    for (i = 0; i < nitems; i++) {
      s[0] = MODBFRAME[j++]
      s[1] = MODBFRAME[j++]
      s[2] = MODBFRAME[j++]
      s[3] = MODBFRAME[j]
      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
      data[i] = x
    }
  }
} '

#%IU% (<data>, <nitems>)
#%MDESC%
#  Extracts <nitems> data from the binary frame in MODBFRAME and stores them
#  in the array <data>.%BR%
#
def modb_rtu__data(data, nitems) '{
  local funct i j aux 

  modb_rtu__debug "modb_rtu__data"
  funct = MODBFRAME[1]
  j = 3 # where the answer starts
  if (funct == 1 || funct == 2) {
    for (i = 0; i < nitems; i++) {
      if ((i % 8) == 0) {
        aux = MODBFRAME[j++]
      }
      data[i] = aux & 0x01
      aux >>= 1
    }
  } else if (funct == 3 || funct == 4) {
    for (i = 0; i < nitems; i++) {
      aux = MODBFRAME[j++] << 8
      aux |= MODBFRAME[j++]
      data[i] = aux
    }
  } else if (funct == 5) {
      data[0] = MODBFRAME[4]
  } else if (funct == 6) {
      aux = MODBFRAME[4] << 8
      aux |= MODBFRAME[5]
      data[0] = aux
  }
} '


#%IU% (<nitems>, <data>)
#%MDESC%
#  Checks that nitems is in the (1, 0xFFFF) range and that data is an
#  array of the right capacity.%BR%
#
def modb_rtu__checkpar(nitems, data) '{

   modb_rtu__debug "modb_rtu__checkpar"
   if (MODBFRAME[1] != 8)
     if (nitems <= 0 || nitems > 0xFFFF || nitems != int(nitems)) {
        if (MODB_ERR != -1)
           print " MODBUS: Bad data parameters (number of items)"
        return(-(MODB_ERR = 1003))
     } 
   if (!(whatis("data") & 0x01010000)) {
      if (MODB_ERR != -1)
         print " MODBUS: Bad data parameters (not an array)"
      return(-(MODB_ERR = 1003))
   } 
   return(0)

}'


#%IU% (<node>, <funct>, <word1>, <word2>, <data>)
#%MDESC%
#  Builds a modbus frame from the input parameters <funct>, <word1>, <word2>
#  and <data>. The frame is stored in the global byte array MODBFRAME[]
#  starting from the position 1.%BR%
#
def modb_rtu__frame(node, funct, word1, word2, data) '{
  local addr i j byt shft, len
  local ubyte array CRC[2]
  j = 0

  modb_rtu__debug "modb_rtu__frame - function", funct

  addr = MODBUS[node]["addr"]
  if (addr < 0) {
    if (MODB_ERR != -1)
       print " MODBUS: Not a valid slave node:", node
     return(-(MODB_ERR = 1004))
  }
  if (funct == 7) {
    len = 2
    global ubyte array MODBFRAME[len + 2]
    MODBFRAME[0] = addr
    MODBFRAME[1] = funct
    MODBUS["anslen"] = 3
    CRC = modb_rtu_CRC(MODBFRAME)
    MODBFRAME[len] = CRC[1]
    MODBFRAME[len + 1] = CRC[0]
    return(0)
  } else if (funct == 8) {
    len = 6
    global ubyte array MODBFRAME[len + 2]
    MODBFRAME[0] = addr
    MODBFRAME[1] = funct
    MODBFRAME[2] = word1 >> 8
    MODBFRAME[3] = word1 & 0xFF
    MODBFRAME[4] = word2 >> 8
    MODBFRAME[5] = word2 & 0xFF
    CRC = modb_rtu_CRC(MODBFRAME)
    MODBUS["anslen"] = 6
    MODBFRAME[len] = CRC[1]
    MODBFRAME[len + 1] = CRC[0]
    return(0)
  } else if (funct == 1 || funct == 2) {
    len = 6
    global ubyte array MODBFRAME[len + 2]
    MODBFRAME[0] = addr
    MODBFRAME[1] = funct
    MODBFRAME[2] = word1 >> 8
    MODBFRAME[3] = word1 & 0xFF
    MODBFRAME[4] = word2 >> 8
    MODBFRAME[5] = word2 & 0xFF
    CRC = modb_rtu_CRC(MODBFRAME)
    MODBFRAME[len] = CRC[1]
    MODBFRAME[len + 1] = CRC[0]
    MODBUS["anslen"] = 4 + int((word2 - 1) / 8)
    return(0)
  } else if (funct == 3 || funct == 4) {
    len = 6
    global ubyte array MODBFRAME[len + 2]
    MODBFRAME[0] = addr
    MODBFRAME[1] = funct
    MODBFRAME[2] = word1 >> 8
    MODBFRAME[3] = word1 & 0xFF
    MODBFRAME[4] = word2 >> 8
    MODBFRAME[5] = word2 & 0xFF
    if (word2 > 125) {
      eprint "too many words to read"
      exit
    }
    CRC = modb_rtu_CRC(MODBFRAME)
    MODBFRAME[len] = CRC[1]
    MODBFRAME[len + 1] = CRC[0]
    MODBUS["anslen"] = 3 + word2 * 2
    return(0)
  } else if (funct == 5) {
    len = 6
    global ubyte array MODBFRAME[len + 2]
    MODBFRAME[j++] = addr
    MODBFRAME[j++] = funct
    MODBFRAME[j++] = word1 >> 8
    MODBFRAME[j++] = word1 & 0xFF
    MODBFRAME[j++] = word2 & 0xFF
    MODBFRAME[j++] = 0
    CRC = modb_rtu_CRC(MODBFRAME)
    MODBFRAME[j++] = CRC[1]
    MODBFRAME[j++] = CRC[0]
    MODBUS["anslen"] = 3
    return(0)
  } else if (funct == 6) {
    len = 6
    global ubyte array MODBFRAME[len + 2]
    MODBFRAME[j++] = addr
    MODBFRAME[j++] = funct
    MODBFRAME[j++] = word1 >> 8
    MODBFRAME[j++] = word1 & 0xFF
    MODBFRAME[j++] = word2 >> 8
    MODBFRAME[j++] = word2 & 0xFF
    CRC = modb_rtu_CRC(MODBFRAME)
    MODBFRAME[j++] = CRC[1]
    MODBFRAME[j++] = CRC[0]
    MODBUS["anslen"] = 6
    return(0)
  } else if (funct == 16) {
    if (word2 > 125) {
      eprint "too many words to write"
      exit
    }
    len = 7 + 2 * word2
    global ubyte array MODBFRAME[len + 2]
    MODBFRAME[j++] = addr
    MODBFRAME[j++] = funct
    MODBFRAME[j++] = word1 >> 8
    MODBFRAME[j++] = word1 & 0xFF
    MODBFRAME[j++] = word2 >> 8
    MODBFRAME[j++] = word2 & 0xFF
    MODBFRAME[j++] = (word2 & 0xFF) * 2
    for (i = 0; i < word2; i++) {
      MODBFRAME[j++] = data[i] >> 8
      MODBFRAME[j++] = data[i] & 0xFF
    }
    CRC = modb_rtu_CRC(MODBFRAME)
    MODBFRAME[j++] = CRC[1]
    MODBFRAME[j++] = CRC[0]
    MODBUS["anslen"] = 6
    return(0)
#  } else if (funct == 17) {
#    len = 2
#    global ubyte array MODBFRAME[len + 2]
#    MODBFRAME[j++] = addr
#    MODBFRAME[j++] = funct
#    CRC = modb_rtu_CRC(MODBFRAME)
#    MODBFRAME[j++] = CRC[1]
#    MODBFRAME[j++] = CRC[0]
#    MODBUS["anslen"] = 6
#    return(0)
  }
  return(-(MODB_ERR = 99))
}'


#%UU%(len)
#%MDESC%
#  This macro calculates the CRC over the present MODBFRAME. Lenght is the argument.
#  If an error happens, the macro returns -MODB_ERR (see error codes).%BR%
#
def modb_rtu_CRC(arr) '{
  #/* CRC runs cyclic Redundancy Check Algorithm on input z_p */
  #/* Returns value of 16 bit CRC after completion and */
  #/* always adds 2 crc bytes to message */
  #/* returns 0 if incoming message has correct CRC */
  local ubyte array CRC[2]
  local lcrc, next, carry, n crch, crcl, i, len
  CRC[:]= 0xff
  lcrc  = 0xffff
  len = array_op("cols", arr) - 2
  modb_rtu__debug "modb_rtu_CRC"
  for (i = 0; i < len; i++) {
    next = arr[i]
    lcrc ^= next;
    for (n = 0; n < 8; n++) {
      carry = lcrc & 1;
      lcrc >>= 1;
      if (carry) {
        lcrc ^= 0xA001;
      }
    }
  }
  modb_rtu__debug "modb_rtu_CRC", sprintf("%04x", lcrc)
  CRC[0] = lcrc / 256;
  CRC[1] = lcrc % 256
  MODB_ERR = 0
  return(CRC)
} '


#%IU% (<node>)
#%MDESC%
#  Sends a message to a slave node and gets the answer back.
#  The message is constructed from a valid modbus frame previously stored in
#  the byte array MODBFRAME[]. The answer from the slave is stored also in
#  MODBFRAME[], overwritting the initial content. The return value is the
#  length of the answer frame or -MODB_ERR in case of error.%BR%
#
def modb_rtu__put(node) '{
  local comm device

  modb_rtu__debug "modb_rtu__put address:", (MODBFRAME[2] <<8) + MODBFRAME[3]
  comm   = MODBUS[node]["comm"]
  device = MODBUS[node]["dev"]

  if (comm == "serial") {
    ser_par(device, "flush", 2)
    if (ser_put(device, MODBFRAME) == -1) {
      if (MODB_ERR != -1)
        print " MODBUS: Transmission error (ser_put)"
      return(-(MODB_ERR = 1008))
    }
  } else {
    local len, str, i
    len = array_op("cols", MODBFRAME)
    ESRF_ERR = -1
    for (i = 0; i < len; i ++) {
      if (esrf_io(device, "DevSerWriteChar", MODBFRAME[i]) == -1) {
        if (MODB_ERR != -1)
          print " MODBUS: Transmission error (esrf_io)"
        return(-(MODB_ERR = 1008))
      }
    }
  }
  modb_rtu__debug "modb_rtu__put", MODBFRAME
  MODBUS[node]["funct"] = MODBFRAME[1]
  return(0)
}'

def modb_rtu__get(node, funct) '{
  local retval

  modb_rtu__debug "modb_rtu__get"
  if (funct != MODBUS[node]["funct"]) {
    if (MODB_ERR != -1)
      print " MODBUS: Function mismatch"
    return(-(MODB_ERR = 1007))
  }

  retval = modb_rtu__getlow(node)
  if (!retval)
    retval = modb_rtu__chkanswer(funct)

  if (retval == -1007) {
    print "Retry: ", modb_rtu__getlow(node)
  }
  return(retval)
}'

def modb_rtu__getlow(node) '{
  local comm device i
  comm   = MODBUS[node]["comm"]
  device = MODBUS[node]["dev"]

  local len, gotten
  len = MODBUS["anslen"]  # is the lenght of the string sent, wo CRC
  local ubyte array __mb_str[len + 2]
  local ubyte array __error[5]
  modb_rtu__debug "modb_rtu__getlow len=" len + 2
  # in order to capture an error, we have to catch the function byte, which
  # in case of an error, will have the most significant bit set! Then get the
  # rest of the answer.
  #
  # After non conclusive tests with multiple spec sessions reading from the same
  # serial line, the read needs to be done in ONE go. Otherwise a different spec
  # session might jump in and catch the answer of this one. So read all the
  # bytes expected and worry about errors later.
  if (comm == "serial") {
    local i
    gotten = ser_get(device, __mb_str)
    # of course, if the error bit is set, we are going to get a time out, 
    # but we will have to live with that, I guess.
    if (__mb_str[1] & 128) { # this is a modbus data error
      __error = __mb_str[0:4]
      # now check the error code (in byte 3)
      if (__error[2] == 3) {
        eprint "Modbus data error: Illegal Data Value"
        eprint "  The value referenced in the data field is not allowable in the"
        eprint "  addressed slave location", (MODBFRAME[2] << 8) + MODBFRAME[3]
        return(-(MODB_ERR = 1003))
      }
      else if (__error[2] == 2) {
        eprint "Modbus data error: Illegal Data Address"
        eprint "  The address referenced in the data field is not an"
        eprint "  allowable address for the slave"
        return(-(MODB_ERR = 1003))
      }
      else {
        eprint "Modbus data error: Unknown error", __error[2] "!"
        return(-(MODB_ERR = 1003))
      }
    }
    if (!gotten) {
      gotten = ser_get(device, __mb_str[0:3])
    }    
    modb_rtu__debug "modb_rtu__getlow gotten", gotten, "chars"
    if (__mb_str[1] & 128) { # this is a modbus data error
      __error[0:1] = __mb_str[2:3]
      # now check the error code (in byte 3)
      if (__error[0] == 3) {
        eprint "Modbus data error: Illegal Data Value"
        eprint "  The value referenced in the data field is not allowable in the"
        eprint "  addressed slave location"
        return(-(MODB_ERR = 1003))
      }
      else if (__error[0] == 2) {
        eprint "Modbus data error: Illegal Data Address"
        eprint "  The address referenced in the data field is not an"
        eprint "  allowable address for the slave"
        return(-(MODB_ERR = 1003))
      }
      else {
        eprint "Modbus data error: Unknown error", __error[0] "!"
        return(-(MODB_ERR = 1003))
      }        
    }
    if (gotten == 0) {
      if (MODB_ERR != -1)
        print " MODBUS: No answer from slave node"
      return(-(MODB_ERR = 1005))
    }
  } else {
    ESRF_ERR = -1
    gotten  = esrf_io(device, "DevSerReadChar", (len << 8) | 1, __mb_str)
    if (ESRF_ERR){
      if (MODB_ERR != -1)
        print " MODBUS: No answer from slave node"
      return(-(MODB_ERR = 1005))
    }
    if (__mb_str[1] & 128) { # this is a modbus data error
      __error[0:1] = __mb_str[0:4]
      # now check the error code (in byte 3)
      if (__error[2] == 3) {
        eprint "Modbus data error: Illegal Data Value"
        eprint "  The value referenced in the data field is not allowable in the"
        eprint "  addressed slave location"
        return(-(MODB_ERR = 1003))
      }
      else if (__error[2] == 2) {
        eprint "Modbus data error: Illegal Data Address"
        eprint "  The address referenced in the data field is not an"
        eprint "  allowable address for the slave"
        return(-(MODB_ERR = 1003))
      }
      else {
        eprint "Modbus data error: Unknown error", __error[2] "!"
        return(-(MODB_ERR = 1003))
      }        
    }
  }
  modb_rtu__debug "modb_rtu__getlow: received", __mb_str
  MODBUS[node]["funct"] = 0
  if (err = modb_rtu__chkstr(__mb_str))
    return(err)
  # now recreate MODBFRAME in the right length and copy __mb_str into it.
  global ubyte array MODBFRAME[len + 2]
  MODBFRAME = __mb_str
  return(0)
}'


def __modb_rtu__getdata(node, funct) '{
  local comm device str len

  modb_rtu__debug "__modb_rtu__getdata"
  modb_rtu__debug funct
  comm   = MODBUS[node]["comm"]
  device = MODBUS[node]["dev"]

  if (funct != MODBUS[node]["funct"]) {
     if (MODB_ERR != -1)
        print " MODBUS: Function mismatch"
     return(-(MODB_ERR = 1007))
  }

  if (comm == "serial") {
     str = ser_get(device)
     if (str == "") {
        if (MODB_ERR != -1)
           print " MODBUS: No answer from slave node"
        return(-(MODB_ERR = 1005))
     }
  } else {
     ESRF_ERR = -1
     str = esrf_io(device, "DevSerReadString", 2)
     if (ESRF_ERR){
        if (MODB_ERR != -1)
           print " MODBUS: No answer from slave node"
        return(-(MODB_ERR = 1005))
     }
  }
  MODBUS[node]["funct"] = 0

  if (err = modb_rtu__chkstr(str))
     return(err)
  return(modb_rtu__chkanswer(funct))
}'

#%IU% (<str>)
#%MDESC%
#  Store the answer from the slave into MODBFRAME[].%BR%
#  If the macro detects a MODBUS exception or a CRC error returns -MODB_ERR,
#  otherwise returns the length of the answer frame.%BR% 
#
def modb_rtu__chkstr(str) '{
   local i len hd ld
   local ubyte array CRC[2]
   modb_rtu__debug "modb_rtu__chkstr"
   # check the two first bytes
   if ((str[0] != MODBFRAME[0]) && (str[1] != MODBFRAME[1])) {
      print " MODBUS: Bad answer (first two bytes not identical)."
      print str, MODBFRAME

      return(-(MODB_ERR = 1006))
   }
   len = MODBUS["anslen"] # is the lenght of the string sent, wo CRC
   CRC = modb_rtu_CRC(str)
   if ((CRC[0] != str[len+1]) || (CRC[1] != str[len])) {
      print " MODBUS: Bad answer (CRC error)."
      return(-(MODB_ERR = 1006))
   }
   return(0)
} '

#%IU% ()
#%MDESC%
#  Checks the received frame for a MODBUS exception and returns the error code.
#  If no exception, it returns the length of the answer frame.%BR% 
#
def modb_rtu__chkanswer(funct) '{
   modb_rtu__debug "modb_rtu__chkanswer"
   if (MODBFRAME[1] != funct) {
      if (MODB_ERR != -1)
         print " MODBUS: Function mismatch"
      return(-(MODB_ERR = 1007))
   }
   if (MODBFRAME[1] & 0x80) {
      MODB_ERR = MODBFRAME[2]
      if (MODB_ERR != -1) {
         if (MODB_ERR == 1)
            printf(" MODBUS: Illegal Function.\n")
         else if (MODB_ERR == 2)
            printf(" MODBUS: Illegal Data Address.\n")
         else if (MODB_ERR == 3)
            printf(" MODBUS: Illegal Data Value.\n")
         else {
            printf(" MODBUS: Unknown Exception (%d).\n", MODB_ERR)
         }
      }
      return(-MODB_ERR)
   }
   MODB_ERR = 0
   return(0)
}'

#%UU% 
#%MDESC%
def modb_rtu_debug '{
   if (MODBUS[0]["debug"]) {
      MODBUS[0]["debug"] = 0
      unglobal MODBDEBUGT
      rdef modb_rtu__debug_start ""
      rdef modb_rtu__debug "0==\$6"
      print "MODBUS debug mode is OFF"
   } else {
      MODBUS[0]["debug"] = 1
      global MODBDEBUGT
      rdef modb_rtu__debug_start \'MODBDEBUGT = time()\'
      rdef modb_rtu__debug \'print printf("%7.2f", 1000*(time() - MODBDEBUGT)), "$*" \'
      print "MODBUS debug mode is ON"
   }
}'

if (!(whatis("modb_rtu__debug_start")&2)) rdef modb_rtu__debug_start ''

if (!(whatis("modb_rtu__debug")&2)) rdef modb_rtu__debug '0==$6'

#%MACROS%
#%IMACROS%
#%AUTHOR% P. Fajardo, (Original 5/01). RTU version based on PF 
# modbus.mac: H. Witsch March 2008
#  $Revision: 1.5 $ / $Date: 2009/03/11 13:06:06 $
#%TOC%