esrf

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

#%TITLE% MODBUS.MAC
#%NAME%
#  Macro functions to access slave MODBUS or MODBUS/TCP devices.
#
#%CATEGORY% Generic I/O
#
#%DESCRIPTION%
#  This macro set provides functions to access remote devices using MODBUS
#  protocol over serial lines or MODBUS/TCP over TCP/IP.%BR%
#  %BR%
#  The following MODBUS functions are implemented:
#  %UL%%UL%
#  %LI%  FC1:  Read Coils
#  %LI%  FC2:  Read Input Discretes
#  %LI%  FC3:  Read Multiple Registers
#  %LI%  FC4:  Read Input Registers
#  %LI%  FC5:  Write Coil
#  %LI%  FC6:  Write Single Register
#  %LI%  FC7:  Read Exception Status
#  %LI%  FC11: Get Comm Event Counters
#  %LI%  FC15: Force Multiple Coils
#  %LI%  FC16: Write Multiple Registers
#  %XUL%%XUL%
#
#  The remote devices must behave as MODBUS slave nodes and have to be
#  register with the %B%modb_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 or an
#  exception is received from a slave node, the corresponding error or 
#  exception code 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
#  %XUL%%XUL%
#  The most frequent MODBUS exception codes are:
#  %UL%%UL%
#  %LI%    1 - Illegal Function
#  %LI%    2 - Illegal Data Address
#  %LI%    3 - Illegal Data Value
#  %XUL%%XUL%
#
#%EXAMPLE%
#
#  %B%myslave = modb_addnode(3, 47)%B% %BR%
#  %B%modb_write_reg(myslave, 0x100, 0xFFFF)%B% %BR%
#  This writes the value 0xFFFF in the register 0x100 of the slave with
#  MODBUS address 47 that is connected to the %B%spec%B% serial
#  interface 3.%BR%
#  %B%modb_addnode("id33wagoeh2")%B% %BR%
#  This macro checks the connection with the MODBUS/TCP slave id33wagoeh2 and
#  adds it to the internal node list.
#

#%UU% (<device>, <maddr> [, <name> [, <flags>]])
#%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%
#
#  In the case of slave nodes interfaced by serial line connection, 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%
#  In the case of a MODBUS/TCP connection, <device> must be the network name
#  or IP address of the slave node and the address <maddr> may be meaningless,
#  as it is usually ignored by the slave.%BR%
#
#  If there is an error, the macro returns -MODB_ERR, and the node is not
#  added to the internal list.%BR%
#
def modb_addnode(device, maddr, name, flags) '{
   global MODBUS[]
   ubyte array MODBFRAME[512]
   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")
   } else {
      if (!flags && modb_ipcheck(device) < 0) {
         printf(" MODBUS: Bad IP network (%s).\n", device)
         return(-(MODB_ERR = 1002))
      }
      comm = "tcp"
      MODBUS[0]["tcpport"] = sprintf("%s:502", device)
      answ = sock_par(MODBUS[0]["tcpport"], "connect")? 0 : -1
   }
   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
   
   modb_get_eventcnt(0, data)
   MODBUS[0]["addr"] = 0
   if (MODB_ERR) {
      return(-MODB_ERR)
   }

   for (node = 1; node <= MODBUS[0]["n_nodes"]; node++) {
      if ((name != "" && MODBUS[node] == name) || \
          (MODBUS[node]["dev"] == device && \
          (comm == "tcp" || MODBUS[node]["addr"] == maddr))) {
         if (MODBUS[node] == "")
            printf("Replacing unamed MODBUS node by %s:%d\n", device, maddr-1)
         else if (MODBUS[node] != name)
            printf("Replacing MODBUS node \'%s\' by \'%s\' at %s:%d\n", \
                  MODBUS[node], name, device, maddr-1)

         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
   if (comm = "tcp")
      MODBUS[node]["tcpport"] = MODBUS[0]["tcpport"]

   MODB_ERR = 0
   return(node)
}'

def modb_ipcheck(device) '{
   local subnetwork

   if ((subnet = get_subnet("`hostname`")) > 0 && \
       subnet == get_subnet(device))
      return(1)
   else {
      return(-1)
   }
}'

def get_subnet(computer) '{
   local comm aux aux0 aux1 aux2 

   comm = "nslookup " computer " | grep Address: | tail -1"
   unix(comm, aux)
   if (sscanf(aux, "%s 160.103.%d.%d", aux0, aux1, aux2) == 3)
      return(aux1)
   else
      return(-1)
}'

def modb_is_tcp(device) '{
   local nnodes i

   nnodes = MODBUS[0]["n_nodes"]

   for (i = 1; i <= nnodes; i++) {
      if (MODBUS[i]["dev"] == device && MODBUS[i]["comm"] == "tcp")
         return(i)
   }
   return(-1)
}'

#%UU%
#%MDESC%
#  Displays the list of the current defined MODBUS nodes.%BR%
#
def modbshow '{
   local nnodes i

   nnodes = MODBUS[0]["n_nodes"]

   if (!nnodes) {
      print "No MODBUS nodes currently defined."
      exit
   }
   print "Node   Name              Addr Interface\n"
   print "---------------------------------------------\n"
   for (i = 1; i <= nnodes; i++) {
      printf("%3d  - %-15s   %3d  ", i, MODBUS[i], MODBUS[i]["addr"] - 1)
      if (MODBUS[i]["comm"] == "tcp") {
         print "TCP:    ", MODBUS[i]["dev"]
      } else {
         print "SERIAL: ", MODBUS[i]["dev"]
      } 
   }
}'

#%UU% (<node>, <daddr>, <ncoils>, <data>)
#%MDESC%
#  This macro executes the function FC1:Read Coils 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_addnode()%B%.
#  The parameters <daddr> and <ncoils> contain the address of the first bit
#  (coil) and the total number of bits (coils) to read.%BR%
#  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 with a value of 0 or 1.
#  If an error happens, the macro returns -MODB_ERR (see error codes).%BR%
#
def modb_read_coils(node, daddr, ncoils, data, opts) '{
   modb__debug_start 
   return(modb__read(node, daddr, ncoils, data, opts, 1))
}'

#%UU% (<node>, <daddr>, <ninp>, <data>)
#%MDESC%
#  This macro executes the function FC2:Read Input Discretes 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_addnode()%B%.
#  The parameters <daddr> and <ninp> contain the address of first input bit
#  and the total number of bits to read.%BR%
#  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 with a value of 0 or 1.
#  If an error happens, the macro returns -MODB_ERR (see error codes).%BR%
#
def modb_read_inputs(node, daddr, ninp, data, opts) '{
   modb__debug_start 
   return(modb__read(node, daddr, ninp, data, opts, 2))
}'

#%UU% (<node>, <daddr>, <nreg>, <data>)
#%MDESC%
#  This macro executes the function FC3:Read Multiple Registers 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_addnode()%B%.
#  The parameters <daddr> and <nreg> contain the address of first input
#  register (word) and the total number of registers (words) to read.%BR%
#  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_read_multregs(node, daddr, nreg, data, opts) '{
   modb__debug_start 
   return(modb__read(node, daddr, nreg, data, opts, 3))
}'

#%UU% (<node>, <daddr>, <nreg>, <data>)
#%MDESC%
#  This macro executes the function FC4:Read Input Registers 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_addnode()%B%.
#  The parameters <daddr> and <nreg> contain the address of first input
#  register (word) and the total number of registers (words) to read.%BR%
#  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_read_inpregs(node, daddr, nreg, data, opts) '{
   modb__debug_start 
   return(modb__read(node, daddr, nreg, data, opts, 4))
}'

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

   if (err = modb__checkpar(nitems, data))
      return(err)
   if (!(opts & 0x02)) {
      if (err = modb__frame(node, funct, daddr, nitems))
         return(err)
      if (err = modb__put(node))
         return(err)
   }
   if (!(opts & 0x01)) {
      if (err = modb__get(node, funct))
         return(err)
      modb__data(data, nitems)
      MODB_ERR = 0
      modb__debug 
      return(nitems)
   }
   MODB_ERR = 0
   modb__debug
   return(0)
}'

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

   modb__debug "modb__data"
   funct = MODBFRAME[7]
   j = 9
   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
      }
   }
}'

#%UU% (<node>, <daddr>, <value>)
#%MDESC%
#  This macro executes the function FC5:Write Coil 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_addnode()%B%.
#  The parameter <daddr> contains the address of the bit (coil) to access and
#  <value> contains the logical value (0 or 1) to write.%BR%
#  If an error happens, the macro returns -MODB_ERR (see error codes),
#  otherwise 0.%BR%
#
def modb_write_coil(node, daddr, value, opts) '{
   local err
   modb__debug_start 
   if (!(opts & 0x02)) {
      if (err = modb__frame(node, 5, daddr, value? 0xFF00 : 0x0000))
         return(err)
      if (err = modb__put(node))
         return(err)
   }
   if (!(opts & 0x01)) {
      if (err = modb__get(node, 5))
         return(err)
   }
   MODB_ERR = 0
   modb__debug 
   return(0)
}'

#%UU% (<node>, <daddr>, <value>)
#%MDESC%
#  This macro executes the function FC6:Write Single Register 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_addnode()%B%.
#  The parameter <daddr> contains the address of the word (register) to access
#  and <value> contains the 16-bit value.%BR%
#  If an error happens, the macro returns -MODB_ERR (see error codes),
#  otherwise 0.%BR%
#
def modb_write_reg(node, daddr, value, opts) '{
   local err
   modb__debug_start 
   if (!(opts & 0x02)) {
      if (err = modb__frame(node, 6, daddr, value))
         return(err)
      if (err = modb__put(node))
         return(err)
   }
   if (!(opts & 0x01)) {
      if (err = modb__get(node, 6))
         return(err)
   }
   MODB_ERR = 0
   modb__debug 
   return(0)
}'

#%UU% (<node>, <data>)
#%MDESC%
#  This macro executes the function FC7:Read Exception 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_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_read_status(node, data, opts) '{
   local err
   modb__debug_start 
   if (!(opts & 0x02)) {
      if (err = modb__frame(node, 7))
         return(err)
      if (err = modb__put(node))
         return(err)
   }
   if (!(opts & 0x01)) {
      if (err = modb__get(node, 7))
         return(err)
      data[0] = MODBFRAME[8]
      MODB_ERR = 0
      modb__debug
      return(1)
   } 
   MODB_ERR = 0
   modb__debug 
   return(0)
}'

#%UU% (<node>, <data>)
#%MDESC%
#  This macro executes the function FC11:Get Comm Event Counter 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_addnode()%B%.
#  If no error happens, the node status word and the event count are stored
#  in the first two positions of the array <data> and the macro function
#  returns 2.
#  If an error happens, the macro returns -MODB_ERR (see error codes).%BR%
#
def modb_get_eventcnt(node, data, opts) '{
   local err
   modb__debug_start 
   if (err = modb__checkpar(2, data))
      return(err)

   if (!(opts & 0x02)) {
      if (err = modb__frame(node, 11))
         return(err)
      if (err = modb__put(node))
         return(err)
   }
   if (!(opts & 0x01)) {
      if (err = modb__get(node, 11))
         return(err)
      data[0] = (MODBFRAME[8] << 8) | MODBFRAME[9]
      data[1] = (MODBFRAME[10] << 8) | MODBFRAME[11]
      MODB_ERR = 0
      return(2)
   }
   MODB_ERR = 0
   modb__debug 
   return(0)
}'

#%UU% (<node>, <daddr>, <ncoils>, <data>)
#%MDESC%
#  This macro executes the function FC15:Force Multiple Coils 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_addnode()%B%.
#  The parameters <daddr> and <ncoils> contain the address of first bit (coil)
#  and the total number of bits (coils) to write.
#  The first positions of the array <data> cointain the logical values to
#  write. Each valid position of <data> must contain only one bit with a value
#  of 0 or 1.%BR%
#  If an error happens, the macro returns -MODB_ERR (see error codes),
#  otherwise 0.%BR%
#
def modb_force_coils(node, daddr, ncoils, data, opts) '{
   local err
   modb__debug_start 
   if (!(opts & 0x02)) {
      if (err = modb__checkpar(ncoils, data))
         return(err)
      if (err = modb__frame(node, 15, daddr, ncoils, data))
         return(err)
      if (err = modb__put(node))
         return(err)
   }
   if (!(opts & 0x01)) {
      if (err = modb__get(node, 15))
         return(err)
   }
   MODB_ERR = 0
   modb__debug 
   return(0)
}'

#%UU% (<node>, <daddr>, <nregs>, <data>)
#%MDESC%
#  This macro executes the function FC16:Write Multiple Registers 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_addnode()%B%.
#  The parameters <daddr> and <nregs> contain the address of first word
#  (register) and the total number of words (registers) to write.
#  The first positions of the array <data> cointain the register values to
#  write. Each valid position of <data> must contain a 16-bit word.%BR%
#  If an error happens, the macro returns -MODB_ERR (see error codes),
#  otherwise 0.%BR%
#
def modb_write_regs(node, daddr, nregs, data, opts) '{
   local err

   modb__debug_start 
   if (!(opts & 0x02)) {
      if (err = modb__checkpar(nregs, data))
         return(err)
      if (err = modb__frame(node, 16, daddr, nregs, data))
         return(err)
      if (err = modb__put(node))
         return(err)
   }
   if (!(opts & 0x01)) {
      if (err = modb__get(node, 16))
         return(err)
   }
   MODB_ERR = 0
   modb__debug 
   return(0)
}'


#%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__checkpar(nitems, data) '{

   modb__debug "modb__checkpar"
   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__frame(node, funct, word1, word2, data) '{
   local addr i j byt shft

   modb__debug "modb__frame"

   addr = MODBUS[node]["addr"] - 1
   if (addr < 0) {
      if (MODB_ERR != -1)
         print " MODBUS: Not a valid slave node:", node
      return(-(MODB_ERR = 1004))
   }
   MODBFRAME[0:4] = 0
   MODBFRAME[6] = addr
   MODBFRAME[7] = funct
   if (funct == 7 || funct == 11) {
      MODBFRAME[5] = 2
      return(0)
   }

   MODBFRAME[8] = word1 >> 8
   MODBFRAME[9] = word1 & 0xFF
   MODBFRAME[10] = word2 >> 8
   MODBFRAME[11] = word2 & 0xFF
   if (funct >= 1 && funct <= 6){
      MODBFRAME[5] = 6
      return(0)
   }

   if (funct == 15) {
      MODBFRAME[12] = int((word2 - 1) / 8) + 1
      j = 12
      byt = 0
      for (i = 0; i < word2; i++) {
         shft = i % 8
         byt |= (data[i] & 1) << shft
         if (shft == 7) {
            MODBFRAME[++j] = byt
            byt = 0
         }
      }
      if (shft != 7)
         MODBFRAME[++j] = byt
      MODBFRAME[5] = j - 5
      return(0)
   }

   if (funct == 16) {
      MODBFRAME[12] = 2 * word2
      j = 12
      for (i = 0; i < word2; i++) {
         MODBFRAME[++j] = data[i] >> 8
         MODBFRAME[++j] = data[i] & 0xFF
      }
      MODBFRAME[5] = j - 5
      return(0)
   }
   return(-(MODB_ERR = 99))
}'


#%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__put(node) '{
   local comm device str len

   modb__debug "modb__put"
   comm   = MODBUS[node]["comm"]
   device = MODBUS[node]["dev"]

   if (comm == "tcp") {
      if (MODBUS[node]["funct"]) {
         sock_par(MODBUS[node]["tcpport"], "close")
         MODBUS[node]["funct"] = 0
      }
      len = MODBFRAME[5] + 5

      if (sock_put(MODBUS[node]["tcpport"], MODBFRAME[0:len]) == -1){
         if (MODB_ERR != -1)
            print " MODBUS: Transmission error (socket_io)"
         return(-(MODB_ERR = 1008))
      }
   } else {
      str = modb__buildstr()
      if (comm == "serial") {
         if (MODBUS[node]["funct"]) {
            ser_get(device)
            MODBUS[node]["funct"] = 0
         }
         if (ser_put(device, str) == -1) {
            if (MODB_ERR != -1)
               print " MODBUS: Transmission error (ser_put)"
            return(-(MODB_ERR = 1008))
         }
      } else {
         if (MODBUS[node]["funct"]) {
            esrf_io(device, "DevSerReadString", 2)
            MODBUS[node]["funct"] = 0
         }
         if (esrf_io(device, "DevSerWriteString", str) == -1) {
            if (MODB_ERR != -1)
               print " MODBUS: Transmission error (esrf_io)"
            return(-(MODB_ERR = 1008))
         }
      }
   }
   MODBUS[node]["funct"] = MODBFRAME[7]
   return(0)
}'

def modb__get(node, funct) '{
  local retval

  retval = modb__getdata(node, funct)
  if (retval == -1007) {
     MODBUS[node]["funct"] = 
     print "Retry: ", modb__getdata(node, -1)
  }
}'


def modb__get(node, funct) '{
   local retval

   modb__debug "modb__get"
#if (myexit) {print "EXIT", myexit = 0; exit}
   if (funct != MODBUS[node]["funct"]) {
      if (MODB_ERR != -1)
         print " MODBUS: Function mismatch"
      return(-(MODB_ERR = 1007))
   }

   retval = modb__getlow(node)
   if (!retval)
      retval = modb__chkanswer(funct)

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

def modb__getlow(node) '{
   local comm device str len

   comm   = MODBUS[node]["comm"]
   device = MODBUS[node]["dev"]

   if (comm == "tcp") {
      sock_get(MODBUS[node]["tcpport"], MODBFRAME[0:5])
      len = MODBFRAME[5]
      sock_get(MODBUS[node]["tcpport"], MODBFRAME[6:(5 + len)])

      MODBUS[node]["funct"] = 0
      return(0)

   } else {
      if (comm == "serial") {
         str = ser_get(device)
         if (HDW_ERR || 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__chkstr(str))
         return(err)
      return(0)
   }
}'


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

   modb__debug "modb__get"
   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 == "tcp") {
      sock_get(MODBUS[node]["tcpport"], MODBFRAME[0:5])
      len = MODBFRAME[5]
      sock_get(MODBUS[node]["tcpport"], MODBFRAME[6:(5 + len)])

      MODBUS[node]["funct"] = 0
      return(modb__chkanswer(funct))

   } else {
      if (comm == "serial") {
         str = ser_get(device)
         if (HDW_ERR || 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__chkstr(str))
         return(err)
      return(modb__chkanswer(funct))
   }
}'

#%IU% ()
#%MDESC%
#  Returns an ASCII MODBUS message string build from a modbus frame previously
#  stored in the byte array MODBFRAME[]. The string includes the LRC bytes and
#  all the required control characters.%BR%
#
def modb__buildstr() '{
   local i len lrc str

   modb__debug "modb__buildstr"
   len = MODBFRAME[5] + 5
   str = ""
   lrc = 0
   for (i = 6; i <= len; i++) {
      str = sprintf("%s%02X", str, MODBFRAME[i])
      lrc += MODBFRAME[i]
   }
   lrc = (-(lrc & 0xFF)) & 0xFF

   str = sprintf(":%s%02X\r\n", str, lrc)
   return(str)
}'

#%IU% (<str>)
#%MDESC%
#  Converts an ASCII MODBUS answer in <str> into a byte binary sequence that
#  is stored in MODBFRAME[].%BR%
#  If the macro detects a MODBUS exception or a LRC error returns -MODB_ERR,
#  otherwise returns the length of the answer frame.%BR% 
#
def modb__chkstr(str) '{
   local i len lrc hd ld

   modb__debug "modb__chkstr"
   if (substr(str, 1, 1) != ":") {
      print " MODBUS: Bad answer (no prefix)."
      return(-(MODB_ERR = 1006))
   }
   lrc = 0
   len = int((length(str) - 1) / 2) - 1
   local ubyte array provi[2*len]
   provi = substr(str, 2) "x"
#   provi -= 48 + 7 * (provi >= 65)
   MODBFRAME[5] = len
   for (i = 0; i < len; i++) {
      hd = provi[2*i]
      hd -= 48 + 7 * (hd >= 65)
      ld = provi[2*i + 1]
      ld -= 48 + 7 * (ld >= 65)
      MODBFRAME[i + 6] = (hd << 4) + ld
   }
   if (array_op("sum", MODBFRAME[6:(5+len)]) & 0xFF) {
      print " MODBUS: Bad answer (LCR 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__chkanswer(funct) '{
   modb__debug "modb__chkanswer"
   if ((MODBFRAME[7] & 0x7F) != funct) {
      if (MODB_ERR != -1)
         print " MODBUS: Function mismatch"
      return(-(MODB_ERR = 1007))
   }
   if (MODBFRAME[7] & 0x80) {
      MODB_ERR = MODBFRAME[8] 
      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 modbdebug '{
   if (MODBUS[0]["debug"]) {
      MODBUS[0]["debug"] = 0
      unglobal MODBDEBUGT
      rdef modb__debug_start ""
      rdef modb__debug "0==\$6"
      print "MODBUS debug mode is OFF"
   } else {
      MODBUS[0]["debug"] = 1
      global MODBDEBUGT
      rdef modb__debug_start \'MODBDEBUGT = time()\'
      rdef modb__debug \'print 1000*(time() - MODBDEBUGT), "$*"\'
      print "MODBUS debug mode is ON"
   }
}'

def modb__debug_start ''

def modb__debug '0==$6'

#%MACROS%
#%INTERNALS%
#  ???.
#
#%AUTHOR% P. Fajardo, (Original 5/01).
#  $Revision: 1.2 $ / $Date: 2008/08/12 13:59:19 $
#%TOC%