esrf

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

#%TITLE% INTLCK.MAC
#%NAME%
#  Manages interlock instances in Wago controllers
#
#%CATEGORY% Generic I/O, Other hardware
#
#%OVERVIEW%
# The Wago controllers can implement interlock functionality by means of
# the %B%isgmain%B% utility (or one of its derivatives) that must be running
# in the controller.
# This utility can be configured to associate alarm relays (digital outputs)
# to a variable number of control signals (input/output channels).
# In this context each alarm relay with its associated control signals is what
# is called an \"interlock instance\".
#%BR% %PRE%
#%PRE%
# Each control signal of an interlock instance has a defined "alarm condition". 
# This condition is a defined logic value (ON or OFF) for digital signals and
# a value range (defined by MIN/MAX thresholds) for analog signals.
# Whenever one of the control signal reaches an alarm condition, the interlock
# instance goes into "tripped" state and the alarm relay switches to the alarm
# position. 
# By default the alarm position is OFF (relay open) but this can be inverted
# in the configuration of the interlock instance.
#%BR% %PRE%
#%PRE%
# One can configure more than one interlock instance in a Wago controller.
# The controller stores the configuration in its internal non-volatile memory
# and is completely autonomous: as long it is switched on, the configured
# interlock functions are active.
#%BR% %PRE%
#%PRE%
# The configuration is also stored in a ASCII file in the host computer.
# This makes easy create or modify the configuration by means of an text
# editor, before uploading it into the controller.
# The macro %B%intlck%B% allows displaying and changing the configuration
# file as well as managing the configuration of the interlock instances in
# the controller itself.
#%BR% %PRE%
#%PRE%
# In the configuration file each instance is declared by a "relay line"
# (begining by the literal "relay" keyword) and must be followed by as many
# "control channel lines" as needed.
# The syntax is as follows:
#%DL%
#%DT%relay line:
#%DD% relay <outrelay> [<iflags>] [name <string>]  [# comments ...]
#%DT%control channel line:
#%DD% <chan> <chtype> [<min> <max>] [<chflags>] [# comments ...]
#%XDL%
# <outrelay> is the channel descriptor for the alarm relay. It should be the
# logical name declared in the device server.
# Conventional subarray syntax is accepted (e.g. psalarm[2]).
#%BR% %PRE%
#%PRE%
# The instance flags <iflags> is optional and can be any combination of the
# literal keywords INVERTED, STICKY and NOFORCE separated by whitespaces.%BR%
# By default the alarm relay is normally ON (closed) and switches OFF (opens)
# when the instance trips. This behaviour can be inverted with the INVERTED
# flag.%BR%
# If the alarm condition disappears after the instance has tripped, by
# default the trip state is automatically cleared and the alarm relay
# switches back to the normal position.
# If the STICKY flag is set, the trip state is not cleared until the interlock
# instance is reset from the host computer (see %B%intlck%B%%B%reset%B%).%BR%
# By default when there is no alarm condition, the alarm relay is forced to
# the normal position and cannot be switched externally. The NOFORCE flag
# relaxes this constraint. In any case when the instance trips, the relay is
# always forced into the alarm state.
#%BR% %PRE%
#%PRE%
# <string> is an arbitrary description string that is stored in the
# controller for identification purposes. It is optional and must always be
# at the end of the line (before any optional comment).
#%BR% %PRE%
#%PRE%
# <chan> is the control channel descriptor. It should be the logical name
# declared in the device server.
# Conventional subarray syntax is accepted (e.g. temp[0]).
#%BR% %PRE%
#%PRE%
# The channel type <chtype> is a mandatory two-letter code that indicates
# the type of I/O channel. At least the following codes should be valid:
# IB (input bit), OB (output bit), IW (input word), OW (output word),
# TC (thermocouple), IV (input voltage), OV (output voltage).
#%BR% %PRE%
#%PRE%
# The thresholds <min> and <max> are mandatory for analog channels (IW, OW,
# IV, OV, TC) and define the alarm condition.%BR%
# By default the normal condition occurs when the analog signal is between
# the <min> and <max> values. When the signal is out of that range an
# alarm condition is met and the interlock instance trips.
#%BR% %PRE%
#%PRE%
# The channel flags <chflags> is an optional field and can be any
# combination of the literal keywords INVERTED and STICKY separated by
# whitespaces.%BR%
# The logic of alarm condition can be inverted with the INVERTED flag. By
# default digital control channels are normally ON and switch into alarm
# condition when they become OFF, and analog channels trip when their value
# is out of the min/max thresholds as explained above.%BR%
#%BR% %PRE%
#%PRE%
# The STICKY flag has the same function than when it is used as an instance
# flag, but in this case it is associated to a particular channel and only
# takes effect when this channel is the one that trips.
#%BR% %PRE%
#%PRE%
#
#%EXAMPLE%
#  %DL%
#  %DT%intlck
#  %DD%Displays a summary of the current configuration
#  %DT%intlck show wcid33a
#  %DD%Displays the detailed configuration of the station wcid33a
#  %DT%intlck reset wcid33a 1
#  %DD%Resets the interlock instance #1 in wcid33a
#  %XDL%
#
#%DEPENDENCIES%
# This macroset uses the device server class %B%Wagods%B% and the utilities
# in the macro file %B%wagocore.mac%B%.%BR%
# Older methods used to communicate with the Wago controllers are not
# supported.

jtdo("wagocore")

def intlck__loadtype(table) '{
   table["IB"] = "Input bit"
   table["OB"] = "Output bit";
   table["IW"] = "Input word"
   table["OW"] = "Output word";
   table["TC"] = "Thermocouple"; table["TC"]["scale"] = 0.1
   table["IV"] = "10V input";    
   table["OV"] = "10V output";   
}'

def intlck__loadflag(table) '{
   table["tbit"]["digital"]  = 0x0001
   table["tbit"]["output"]   = 0x0002
   table["cbit"]["unsigned"] = 0x0004
   table["cbit"]["sticky"]   = 0x0008
   table["cbit"]["STICKY"]   = 0x0008
   table["cbit"]["inverted"] = 0x0010
   table["cbit"]["inv"]      = 0x0010
   table["cbit"]["INV"]      = 0x0010
   table["cbit"]["disabled"] = 0x0020
   table["cbit"]["DISABLED"] = 0x0020
   table["cbit"]["monitor"]  = 0x0040
   table["cbit"]["MONITOR"]  = 0x0040
   table["cbit"]["mon"]      = 0x0040
   table["cbit"]["noforce"]  = 0x0080
   table["cbit"]["NOFORCE"]  = 0x0080
   table["sbit"]["trip"]     = 0x0100
   table["sbit"]["alarm"]    = 0x0200
   table["sbit"]["cfgerr"]   = 0x0400
   table["sbit"]["hdwerr"]   = 0x0800

   table["imask"]   = 0x0098  # instance:     inverted + sticky + noforce
   table["tchmask"] = 0x0003  # channel type:  digital + output
   table["bchmask"] = 0x0038  # bits:  sticky + inverted + disabled
   table["wchmask"] = 0x007c  # words: unsigned + sticky + inverted + 
                              #        disabled + monitor
   table["stmask"]  = 0xff00
}'

#%UU% [<action> [<wcname> [<instance>]]]
#%MDESC%
# Without arguments, this macro scans all the Wago controllers declared in
# the setup file and diplays an information summary concerning the interlock
# instances. 
# If arguments are specified, the macro performs the corresponding actions.
# If there is more than one possible controller and its name is not passed
# as an argument, the macro prompts the user for the controller name.
# Some of the actions require SpecWizard privilegies. If %B%spec%B% is not
# in wizard mode, the user will be prompted for the Wizard's password.
#%BR% %PRE%
#%PRE%
# The possible actions/syntax are the following:
# %DL%
# %DT% intlck new [<wcname>]
# %DD% Creates a new configuration file for the controller <wcname>, opens
# the editor and optionally uploads the configuration into the controller.
# %DT% intlck edit [<wcname>]
# %DD% Opens an existing configuration file in the text editor and
# optionally uploads the configuration into the controller <wcname>.
# %DT% intlck show [<wcname>]
# %DD% Donwloads and displays the interlock configuration stored in the
# controller. If the configuration file differs, it is also
# displayed on the screen.
# %DT% intlck update [<wcname>]
# %DD% Uploads the content of the configuration file into the controller.
# %DT% intlck reset [<wcname> [<instance>]]
# %DD% Resets the selected interlock instance in the controller. Only useful
# when a controller trips in sticky mode.
# If there is more than one instance in the controller and the instance
# number is not specified as an argument, the macro prompts the user for it.
# %XDL%
#
def intlck '{
   local usage action wcname inst

   if (!$#) {
      intlck__scan()
      usage = 1
   } else {
      action = "$1"
      wcname = $# >= 2? "$2" : ""
      if (action == "reset") {
         if ($# > 3) {
            print "Too many parameters."
            usage = 1
         } else {
            if ($# == 3) {
               if ((inst = $3) <= 0) {
                  print "Bad instance number."
                  usage = 1
               }
            } else
               inst = 0
            if (!usage)
               intlck__reset(wcname, inst)
         }
      } else if ($# > 2) {
         print "Too many parameters."
         usage = 1
      } else if (action == "show") {
         intlck__show(wcname)
      } else if (action == "edit") {
         intlck__edit(wcname)
      } else if (action == "update") {
         intlck__update(wcname)
      } else if (action == "new") {
         intlck__newfile(wcname)
      } else {
         print "Bad action."
         usage = 1
      }
      print
   }
   if (usage) {
      print
      print "Usage:  $0"
      print "          or"
      print "        $0 reset [<wagoname> [<instance>]]"
      print "          or"
      print "        $0 <action> [<wagoname>]"
      print "          where <action> is one of: new edit show update"
      exit
   }
}'

#%IU% 
#%MDESC%
#  
#  
def intlck__select(wcname) '{
   local nwago i lastwc

   nwago = list_n(WAGO)
   if (!nwago) {
      print "No Wago controllers declared. Use \'wagosetup\' if needed."
      return("")
   }
   if (wcname != "") {
      if (wcname != int(wcname) && (wcname in WAGO)) 
         return(wcname)
      else {
         print "Not a valid Wago controller: \'" wcname "\'."
         return("")
      }
   }

   if (nwago == 1)
      return(WAGO[1])

   print "Currently declared controllers:\n"
   for (i = 1; i <= nwago; i++) {
      wcname = WAGO[i]
      printf("  #%d %10s" , i, wcname)
      if (WAGO[wcname]["isgmain"])
         print "  [available]"
      else
         print "  [no \"isgmain\"]"
   }
   lastwc = list_item(WAGO, WAGO["lastintlck"])
   if (lastwc == -1)
      lastwc = WAGO[1]

   lastwc = getval("\nWhich wago controller", lastwc)
   if ((wcname = list_item(WAGO, lastwc)) == -1) {
      print "Not a valid Wago controller: \'" lastwc "\'"
      return("")
   } else
      return(WAGO["lastintlck"] = wcname)
}'

#%IU% 
#%MDESC%
#  
#  
def intlck__show(wcname) '{
   local file
   local wcinst finst
   local wccfg[] filecfg[] 
   local ret

   if ((wcname = intlck__select(wcname)) == "") return(-1)

   if (!WAGO[wcname]["isgmain"]) {
      print "Interlock program missing in controller \'" wcname "\'."
      return
   }
   if ((wcinst = intlck__download(wcname, wccfg)) >= 0) {
      intlck__showcfg(wcname, wccfg)
   }
   file = intlck__cfgfile(wcname)
   finst = intlck__loadcfg(wcname, file, filecfg)

   if (wcinst <= 0 && finst <= 0)
      return
   if (wcinst <= 0 && finst > 0)
      printf("%s in configuration file. Use \'intlck update\' if needed.\n", \
               intlck__instlbl(finst))
   else if (finst <= 0) {
      print "Missing or wrong configuration file."
      print "Use \'intlck new\' or \'intlck edit\' as needed."
   } else if ((ret=intlck__compcfg(wccfg, filecfg))) {
      intlck__showcfg(wcname, filecfg)
      print "Controller data differs from configuration file:"
      print ret 
      print "\nUse \'intlck update\' if needed."
   }
}'

#%IU% 
#%MDESC%
#  
#  
def intlck__newfile(wcname) '{
   local newfile
   local wcinst finst
   local wccfg[] filecfg[] 

   if ((wcname = intlck__select(wcname)) == "") return(-1)

   newfile = intlck__cfgfile(wcname)

   if (file_info(newfile, "isreg")) {
      if (yesno("Configuration file alredy exists. Do you want to edit it", 1))
         intlck__edit(wcname)
   } else {
      local comminfo

      comminfo = \
         "# Configuration file for Wago controller\n" \
         "#\n" \
         "# Multiple interlock instances can be declared in this file.\n" \
         "#\n" \
         "# Syntax: \n" \
         "#    # First interlock instance\n" \
         "#    relay <outrelay1> {<iflag> ... } [name <name_string>]\n" \
         "#    <chan1> <type> [<min> <max>] {<chflag> ... }\n" \
         "#    <chan2> <type> [<min> <max>] {<chflag> ... }\n" \
         "#     ...\n" \
         "#    <chanN> <type> [<min> <max>] {<chflag> ... }\n" \
         "#  \n" \
         "#    # Second interlock instance\n" \
         "#    relay <outrelay2> {<iflag> ... } [name <name_string>]\n" \
         "#    <chan1> <type> [<min> <max>] {<chflag> ... }\n" \
         "#    <chan2> <type> [<min> <max>] {<chflag> ... }\n" \
         "#     ...\n" \
         "#    <chanM> <type> [<min> <max>] {<chflag> ... }\n" \
         "# \n" \
         "#\n" \
         "# Notes:\n" \
         "#  - <outrelay> must be a digital output channel\n" \
         "#  - Instance flags (<iflag>): inverted, sticky, noforce\n" \
         "#  - Channel types (<type>) supported: IB, OB, IW, OW, TC, IV, OV\n" \
         "#  - <min> <max> values are required for word (analog) values\n" \
         "#  - Channel flags <chflag>: inverted, sticky\n" \
         "#  - Comments at the end of the line are removed\n" \
         "#  - Channels should be specified with logical names with subarray syntax\n" \
         "#\n" \
         "# Example:\n" \
         "#   # PX Minidiff interlock\n" \
         "#   relay mdpermit sticky name Permit to Sample Changer\n" \
         "#     lightin OB inverted  # output driving the pneumatics\n" \
         "#     fldin   OB inverted  # fluo detector pneumatics\n" \
         "#     flsw[1] IB\n" \
         "#     supply  IV 4 6       # 5V power supply monitor\n" \
         "#\n"

      fprintf(newfile, "%s", comminfo)
      close(newfile)
      if (!file_info(newfile, "isreg"))
         print "Can\'t create file: " newfile
      else
         intlck__edit(wcname)
   }
}'

#%IU% 
#%MDESC%
#  Edit and upload the configuration file into the controller
#  
def intlck__edit(wcname) '{
   local file filecfg[]

   if ((wcname = intlck__select(wcname)) == "") return(-1)

   file = intlck__cfgfile(wcname)

   if (!file_info(file, "isreg")) {
      print "Configuration file is missing."
      print "Use \'intlck new\' to create it."
      return
   }
   onwiz 0
   if (spec_par("specwiz")) {
      printf("Editing \"%s\" ... ", intlck__filename(file))
      unix(sprintf("%s %s", SEDITOR, file))
      print "Done"
      intlck__update(wcname)
   } else {
      if (intlck__loadcfg(wcname, file, filecfg) >= 0)
         intlck__showcfg(wcname, filecfg)
   }
}'

#%IU% 
#%MDESC%
#  Upload the configuration file into the controller
#  
def intlck__update(wcname) '{
   local file
   local wcinst finst
   local wccfg[] filecfg[] 

   if ((wcname = intlck__select(wcname)) == "") return(-1)

   file = intlck__cfgfile(wcname)

   if (!file_info(file, "isreg")) {
      print "Configuration file is missing."
      print "Use \'intlck new\' to create it."
      return
   }
   finst = intlck__loadcfg(wcname, file, filecfg, 1)

   if (finst < 0) {
      print "Can\'t upload configuration file"
      return
   }
   if (!WAGO[wcname]["isgmain"]) {
      print "Interlock program not loaded in controller \'" wcname "\'."
      return
   }
   wcinst = intlck__download(wcname, wccfg)

   if (wcinst < 0 || intlck__compcfg(wccfg, filecfg)) {
      local quest

      intlck__showcfg(wcname, filecfg)

      quest = "Upload configuration into controller \'" wcname "\'"
      if (!yesno(quest, 1)) 
         return

      onwiz 0
      if (spec_par("specwiz"))
         wcinst = intlck__upload(wcname, filecfg, 1)
      else 
         wcinst = -1
      if (wcinst < 0)
         print "Error uploading configuration"
      else
         print "Configuration succesfully uploaded"
   } else {
      print "Configuration did not change."
   }
}'

def intlck__instlbl(n) '{
   return sprintf("%s interlock instance%s", n==0? "No" : n, n == 1? "" :  "s")
}'

def intlck__scan() '{
   local nwago i wcname file tab
   local wcinst finst
   local wccfg[] filecfg[] 
   local ret

   tab = sprintf("%15s", "")
   nwago = list_n(WAGO)
   if (!nwago) {
      print "No Wago controllers declared. Use \'wagosetup\' if needed."
      return
   }
   print "Currently declared Wago controllers:"
   for (i = 1; i <= nwago; i++) {
      wcname = WAGO[i]

      file = intlck__cfgfile(wcname)
      finst = intlck__loadcfg(wcname, file, filecfg)
      printf("%10s  -  ", wcname)
      if (WAGO[wcname]["isgmain"]) {
         wcinst = intlck__download(wcname, wccfg)
         if (wcinst < 0) {
            print "Error reading controller configuration. "
         } else {
            printf("%s loaded in the controller\n", intlck__instlbl(wcinst))
            if (finst == -2)
               print tab "Configuration file is missing. " \
                         "Use \'intlck new\' if needed."
            else if (finst == -1)
               print tab "Error reading configuration file."
            else if ((ret=intlck__compcfg(wccfg, filecfg))) {
               printf("%sConfiguration file differs (%s).", tab, ret)
               printf("%sUse \'intlck update\' if needed.", tab)
            }
         }
      } else {
         print "Interlock program missing in controller."
         if (finst >= 0)
            printf("%s%s declared in the configuration file\n", \
                      tab, intlck__instlbl(wcinst))
            print tab "Load \"isgmain\" in controller if needed."
      }
   }
}'

def intlck__cfgfile(wcname) '{
   local dir file

   dir = BLISSADM "/local/spec/userconf/intlck"
   if (!file_info(dir, "isdir")) {
      unix(sprintf("mkdir %s", dir)) 
   }
   file = dir "/"  wcname ".intlck"
   return(file)
}'

def intlck__filename(filepath) '{
   local k[]
   n = split(filepath, k, "/")
   return(k[n-1])
}'

#%IU% (<wcname>, <inst>)
#%MDESC%
#  Resets the instance controller (in case of sticky channels)
#  
def intlck__reset(wcname, inst) '{
   local par[] nres res[]
   local cfgarr[] ninst

   if ((wcname = intlck__select(wcname)) == "") return(-1)
   ninst = intlck__download(wcname, cfgarr)
   if (ninst <= 0) {
      printf("Can\'t read with wago controller \'%s\'\n", wcname)
      return(-1)
   }
   if (ninst == 0) {
      printf("No interlock instances configured in \'%s\'\n", wcname)
      return(-1)
   }
   if (inst <= 0) {
      if (ninst > 1)
         inst = getval(sprintf("Enter instance [1-%d] to reset, default:", ninst), 1)
      else
         inst = 1
   }
   if (inst < 0 || inst > ninst) {
      printf("Interlock instance \'%d\' is out of valid range: [1-%d]\n", inst, ninst)
      return(-1)
   }
   printf("Resetting instance \'%d\' in wago controller \'%s\' ... \n", inst, wcname)
   par[0] = inst
   sleep(.5)
   if ((nres = wc_sendcomm(wcname, "ILCK_RESET", 1, par, res)) < 0) {
      print " ... Failed!"
      return(-1)
   } else {
      print " ... Done!"
      return(0)
   }
}'

#%IU% (<wcname>, <cfgfile>, <cfgarr> [, <verbose>])
#%MDESC%
# Reads the interlock configuration contained in the file <cfgfile> and
# stores it in the array <cfgarr>.
# If the file cannot be read returns -2 and if there is any error returns -1.
# Otherwise returns the number of interlock instances read.
#  
def intlck__loadcfg(wcname, cfgfile, cfgarr, verbose) '{
   local lineraw line file
   local relay n c chan i
   local ntok tok[]
   local flg[] TypeTbl[]

   intlck__loadtype(TypeTbl)
   intlck__loadflag(flg)

   if (!file_info(cfgfile, "isreg")) {
      return (-2)
   }

   filename = intlck__filename(cfgfile)
   if (verbose)
      printf("Reading file \"%s\"\n", filename)

   getline(cfgfile, "close")
   n = 0
   relay = -1
   for (i in cfgarr) delete cfgarr[i]

   cfgarr["filename"] = filename
   while ((line = getline(cfgfile)) != -1) {
      lineraw = ""
      sscanf(line, "%[^\n\r]", lineraw)
      line = ""
      if (sscanf(lineraw, " %[^#]", line) != 1)
         continue
      
      sscanf(line, " %s", c)
      if (c == "relay") {
         n++
         if (i = index(line, "name ")) {
            cfgarr[n]["name"] = substr(line, i + 5)
            line = substr(line, 1, i - 1)
         } else {
            cfgarr[n]["name"] = ""
         }
         ntok = split(line, tok)
         if (ntok < 2) {
            if (verbose)
               printf("  Bad line in %s: \"%s\"\n", cfgfile, lineraw)
            return(-1)
         }
         relay = tok[1]         
         if ((cfgarr[n] = int(relay)) != relay) {
            relaycode = wago__log2hard(relay)
            if (wago__code2type(relaycode >> 16) != "OB"){
               if (verbose)
                  printf("  Not a digital output: \"%s\"\n", relay)
               return(-1)
            }
            cfgarr[n] = relaycode & 0x0000ffff
         }
         cfgarr[n]["flags"] = 0
         for (i = 2; i < ntok; i++) {
            local iflag 

            iflag = flg["cbit"][tok[i]]
            if (!(iflag & flg["imask"])) {
               if (verbose)
                  printf("  Bad flag in %s: \"%s\"\n", cfgfile, lineraw)
               return(-1)
            }
            cfgarr[n]["flags"] |= iflag
         }
         cfgarr[n]["nchan"] = 0
         nch = 1000 * n

      } else {
         local chan chancode type gtype flags flgmask chflg
         local loth hith offset scale

         nch++
         cfgarr[n]["nchan"]++
         if (relay < 0) {
            if (verbose)
               printf("  No relay output specified in \"%s\"\n", cfgfile)
            return(-1)
         }
         ntok = split(line, tok)
         if (ntok < 2) {
            if (verbose)
               printf("  Bad line in %s: \"%s\"\n", cfgfile, lineraw)
            return(-1)
         }
         chan = tok[0]
         type = tok[1]
         if (!(type in TypeTbl)) {
            if (verbose)
               printf("  Bad I/O type in %s: \"%s\"\n", cfgfile, lineraw)
            return(-1)
         }
         flags = (substr(type, 1, 1) == "O")? flg["tbit"]["output"] : 0x00
         if (type == "IB" || type == "OB") {
            flags |= flg["tbit"]["digital"]
            gtype = type
         } else
            gtype = (!flags)? "IW" : "OW"

         if ((cfgarr[nch] = int(chan)) != chan) {
            chancode = wago__log2hard(chan)
            if (wago__code2type(chancode >> 16) != gtype){
               if (verbose) {
                  printf("  Bad line in %s: \"%s\"\n", cfgfile, lineraw)
                  printf("  Incompatible channel type: \"%s\" can\'t be \"%s\"\n", \
                            chan, TypeTbl[type])
               }
               return(-1)
            }
            cfgarr[nch] = chancode & 0x0000ffff
         }
         if (flags & flg["tbit"]["digital"]){
            i = 2
            flgmask = flg["bchmask"]
         } else {
            loth = tok[2]
            hith = tok[3]
            if ((loth + 0) != loth || (hith + 0) != hith || ntok < 4) {
               if (verbose)
                  printf("  Bad line in %s: \"%s\"\n", cfgfile, lineraw)
               return(-1)
            }
            offset = TypeTbl[type]["offset"]
            if (type == "IV" || type == "OV") {
               scale  = wago__log2scale(chan) 
            } else {
               if((scale  = TypeTbl[type]["scale"]) == 0) {
                  scale = 1
               }
            }
            cfgarr[nch]["loth"] = int((loth - offset) / scale)
            cfgarr[nch]["hith"] = int((hith - offset) / scale)
            i = 4
            flgmask = flg["wchmask"]
         }
         for (; i < ntok; i++) {
            chflg = flg["cbit"][tok[i]]
            if (!(chflg & flgmask)) {
               if (verbose)
                  printf("  Bad flag \'%s\' in %s: \"%s\"\n", tok[i],cfgfile, lineraw)
               return(-1)
            }
            flags |= chflg

            # handle monitor flag which requires: dac_channel scale offset
            if(chflg & flg["cbit"]["monitor"]) {
               # Minimum test on tok existance
               if((i+1) >= ntok) {
                   if (verbose) {
                      printf("  Missing dac channel in %s: \"%s\"\n", \
                         cfgfile, lineraw)
                   }
                   return(-1)
               }
               if((i+2) >= ntok) {
                   if (verbose) {
                      printf("  Missing scale factor for dac in %s: \"%s\"\n", \
                         cfgfile, lineraw)
                   }
                   return(-1)
               }
               if((i+3) >= ntok) {
                   if (verbose) {
                      printf("  Missing offset for dac in %s: \"%s\"\n", \
                         cfgfile, lineraw)
                   }
                   return(-1)
               }
               # TODO: add test on type of chancode
               chancode = wago__log2hard(tok[i+1])
               cfgarr[nch]["dac"] = chancode & 0x0000ffff
               cfgarr[nch]["dac_scale"] = tok[i+2]
               cfgarr[nch]["dac_offset"] = tok[i+3]
               i+=3
            }
         }
         cfgarr[nch]["flags"] = flags
         cfgarr[nch]["type"]  = type
      }
   }
   getline(cfgfile, "close")

   return(cfgarr["n"] = n)
}'

#%IU% (<wcname>, <cfgarr> [, <verbose>])
#%MDESC%
# Loads the interlock configuration stored in the array <cfgarr> into the
# Wago controller <wcname>. Any previous configuration in <wcname> is
# deleted. 
# If there is any error returns -1. Otherwise returns the number of
# interlock instances configured.
#  
def intlck__upload(wcname, cfgarr, verbose) '{
   local npar par[] nres res[] n nch i imsk
   local flg[]
   local maxnlen nlen

   intlck__loadflag(flg)

   if (verbose)
      print "Uploading interlock configuration into \'" wcname "\'"

   # --- check if interlock instance exist
   par[0] = WAGOPLC["#func"]["INTERLOCK"] 
   if ((nres = wc_sendcomm(wcname, "ACTIVE", 1, par, res)) < 0) {
      return(-1)
   } else if (res[1] < cfgarr["n"]) {
      if (verbose)
         printf("No enough interlock instances available in \'%s\'.\n", wcname)
      return(-1)
   }
   n = res[1]
   imsk = res[2]
   for (i = 1; i <= n; i++, imsk >>= 1) {
      # Delete previous instances
      if (imsk & 0x0001){
         par[0] = i
         wc_sendcomm(wcname, "ILCK_DELETE", 1, par, res)
      }
   }

   for (n = 1, maxnlen=0; n <= cfgarr["n"]; n++) {
      # Create a new instance
      par[0] = cfgarr[n]
      par[1] = cfgarr[n]["flags"]
      if ((nres = wc_sendcomm(wcname, "ILCK_CREATE", 2, par, res)) < 0) {
         print "Error creating instance " n " in station \'" wcname "\'."
         return(-1)
      }
      par[0] = inst_n = res[0]
      # workaround of WAGO internal storage of the name
      nlen   = length(cfgarr[n]["name"])
      if(nlen > maxnlen) { maxnlen = nlen }
      par[1] = sprintf("%-*s", maxnlen, cfgarr[n]["name"])
      if ((nres = wc_sendcomm(wcname, "ILCK_SETNAME", 2, par, res)) < 0) {
         print "Error setting name for interlock instance " n \
               " in station \'" wcname "\'."
         return(-1)
      }
      # Donwload the channel list
      for (i = 1,nch = 1000 * n + 1; i <= cfgarr[n]["nchan"]; i++, nch++) {
         j = 0
         par[j++] = inst_n
         par[j++] = cfgarr[nch]["flags"]
         par[j++] = cfgarr[nch]
         if (!(par[1] & flg["tbit"]["digital"])) {
            par[j++] = cfgarr[nch]["loth"]
            par[j++] = cfgarr[nch]["hith"]
            par[j++] = cfgarr[nch]["type"]
         }
         # handle monitor flag
         if (par[1] & flg["cbit"]["monitor"]) {
            float array f[1]
            short array s[2]

            par[j++] = cfgarr[nch]["dac"]

            f[0] = cfgarr[nch]["dac_scale"]
            array_copy(s, f)
            par[j++] = s[0]
            par[j++] = s[1]

            f[0] = cfgarr[nch]["dac_offset"]
            array_copy(s, f)
            par[j++] = s[0]
            par[j++] = s[1]
         }
         npar = j

         if ((nres = wc_sendcomm(wcname, "ILCK_ADDCHAN", npar, par, res)) < 0) {
            print "Error configuring channel " i " instance " n\
                  " in \'" wcname "\'."
            return(-1)
         }
      }
   }
   return(cfgarr["n"])
}'

#%IU% (<wcname>, <cfgarr> [, <verbose>])
#%MDESC%
# Reads the current configuration from the Wago controller <wcname>
# and stores it in the array <cfgarr>.
# If there is any error returns -1. Otherwise returns the number of
# interlock instances configured.
#  
def intlck__download(wcname, cfgarr, verbose) '{
   local npar par[] nres res[] n nch i
   local flg[]

   intlck__loadflag(flg)

   if (verbose)
      print "Downloading interlock configuration from \'" wcname "\'"

   for (i in cfgarr) delete cfgarr[i]

   # --- check if interlock instance exist
   par[0] = WAGOPLC["#func"]["INTERLOCK"] 
   if ((nres = wc_sendcomm(wcname, "ACTIVE", 1, par, res)) < 0) {
      return(-1)
   }
   cfgarr["n"] = res[1] - res[0]

   for (n = 1; n <= cfgarr["n"]; n++) {
      # Read instance n data
      par[0] = n
      if ((nres = wc_sendcomm(wcname, "ILCK_GETCONF", 1, par, res)) < 0) {
         print "Error getting configuration: instance " n " in \'" wcname "\'."
         return(-1)
      }
      cfgarr[n] = res[0]
      cfgarr[n]["flags"] = res[1]
      cfgarr[n]["nchan"] = res[2]
      cfgarr[n]["name"] = wc_getstring(wcname, "ILCK_GETNAME", 1, par)
      if ((nres = wc_sendcomm(wcname, "ILCK_GETSTAT", 1, par, res)) < 0) {
         print "Error getting status of instance " n " in \'" wcname "\'."
         return(-1)
      }
      cfgarr[n]["status"] = res[0] & flg["stmask"]
      cfgarr[n]["value"] = res[1]
      # Upload the channel list
      for (i = 1; i <= cfgarr[n]["nchan"]; i++) {
         nch = 1000 * n + i
         par[1] = i
         if ((nres = wc_sendcomm(wcname, "ILCK_GETCONF", 2, par, res)) < 0) {
            print "Error getting configuration: channel " i " instance " n\
                  " in \'" wcname "\'."
            return(-1)
         }
         cfgarr[nch] = res[1]
         cfgarr[nch]["flags"] = flags = res[0]
         if (flags & flg["tbit"]["digital"]) {
            cfgarr[nch]["type"] = flags & flg["tbit"]["output"]? "OB" : "IB"
            cfgarr[nch]["value"] = res[2]
         } else {
            cfgarr[nch]["loth"] = res[2]
            cfgarr[nch]["hith"] = res[3]
            cfgarr[nch]["type"] = wago__code2type(res[4])
            cfgarr[nch]["value"] = res[5]

            # handle monitor flag which supplies: dac_channel scale offset
            if (flags & flg["cbit"]["monitor"]) {
               cfgarr[nch]["dac"] = res[6]

               float array f[1]
               short array s[2]

               s[0] = res[7]
               s[1] = res[8]
               array_copy(f, s)
               cfgarr[nch]["dac_scale"] = f[0]

               s[0] = res[9]
               s[1] = res[10]
               array_copy(f, s)
               cfgarr[nch]["dac_offset"] = f[0]
            }
         }
      }
   }
   return(cfgarr["n"])
}'

#%IU% (string)
#%MDESC%
# Return the given string without useless whitespaces.
#
def intlck_trim(str) '{
   local  b l 

   l = length(str)
   if(l == 0) {
      return ""
   }
   string array str_a[l]
   str_a = str
   l = l-1
   for(;(l>=0)&&(str_a[l]==0x20);l--);
   for(b=0;(b<l)&&(str_a[b]==0x20);b++);

   return(substr(str,b+1,(l-b)+1))
}'

#%IU% (<cfg1>, <cfg2>)
#%MDESC%
# Compares the interlock configurations stored in the associative arrays
# conf1[] and conf2[]. If there is any difference returns 1. If both
# configurations are identical this macro returns 0.
#  
def intlck__compcfg(cfg1, cfg2) '{
   local n_inst inst  n_chan chan ch
   local fmask flg[]

   intlck__loadflag(flg)
   fmask = flg["tchmask"] | flg["bchmask"] | flg["wchmask"]

   if (cfg1["n"] != cfg2["n"])
      return("difference in ninst")

   n_inst = cfg1["n"]
   for (inst = 1; inst <= n_inst; inst++) {
      if (cfg1[inst] != cfg2[inst] || \
          intlck_trim(cfg1[inst]["name"]) != intlck_trim(cfg2[inst]["name"])|| \
          (cfg1[inst]["flags"] ^ cfg2[inst]["flags"]) & flg["imask"] || \
          cfg1[inst]["nchan"] != cfg2[inst]["nchan"]) {
         return("differences in instance: name, flags, nchan")
      }

      n_chan = cfg1[inst]["nchan"]
      for (chan = 1; chan <= n_chan; chan++) {
         ch = 1000 * inst + chan
         if (cfg1[ch] != cfg2[ch] || \
             (cfg1[ch]["flags"] ^ cfg2[ch]["flags"]) & fmask || \
             (!(cfg1[ch]["flags"] & flg["tbit"]["digital"]) &&  \
               (cfg1[ch]["loth"] != cfg2[ch]["loth"] ||         \
                cfg1[ch]["hith"] != cfg2[ch]["hith"]))) {
            return("differences in channel: flags, loth, hith")
         }
         if (cfg1[ch]["flags"] & flg["cbit"]["monitor"] &&      \
             (cfg1[ch]["dac"] != cfg2[ch]["dac"] ||             \
             ((cfg1[ch]["dac_scale"]  - cfg2[ch]["dac_scale"]) > 0.001) || \
             ((cfg1[ch]["dac_offset"] - cfg2[ch]["dac_offset"]) > 0.001))) {
            return("differences in channel monitor: dac, dac_scale, dac_offset")
         }
      }
   }
   return(0)
}'


#%IU% (<wcname>, <cfgarr>)
#%MDESC%
# Displays the interlock configurations stored in the associative array
# cfgarr[].
#  
def intlck__showcfg(wcname, cfgarr) '{
   local n_inst inst flags f showstat status
   local nchan chan ch 
   local type name args offset scale val
   local flg[] TypeTbl[]
   local pout

   intlck__loadtype(TypeTbl)
   intlck__loadflag(flg)

   if ("filename" in cfgarr)
      printf("\nConfiguration file \'%s\':\n", cfgarr["filename"])
   else
      printf("\nCurrent configuration in \'%s\':\n", wcname)
   n_inst = cfgarr["n"]
   printf("%s\n", intlck__instlbl(n_inst))
   for (inst = 1; inst <= n_inst; inst++) {
      printf("  Instance #%d   Name: %s\n", inst, cfgarr[inst]["name"])
      pout = sprintf("    Alarm relay = %s ", \
                wago__hard2log(wcname, cfgarr[inst], "OB"))
      flags = cfgarr[inst]["flags"] & flg["imask"]
      if (flags) {
         for (f in flg["cbit"]){
            if (flags & flg["cbit"][f]) {
               pout = sprintf("%s %s", pout, f)
               flags &= ~flg["cbit"][f]
            }
         }
      }
      if (showstat = ("status" in cfgarr[inst])) 
         pout = sprintf("%-65s [%s]", pout, cfgarr[inst]["value"]? "ON" : "OFF")
      print pout
      if (showstat) {
         status = cfgarr[inst]["status"]
         printf("    State = %s\n", status & flg["sbit"]["trip"]? \
                                         "TRIPPED" : "NOT TRIPPED")
      }
      n_chan = cfgarr[inst]["nchan"]
      printf("    %d channels configured:\n", n_chan)
      for (chan = 1; chan <= n_chan; chan++) {
         ch = 1000 * inst + chan
         flags = cfgarr[ch]["flags"] 
         status = (!showstat)? "" : sprintf("%s%s%s%s", \
               (flags & flg["sbit"]["hdwerr"])? "H":".", \
               (flags & flg["sbit"]["cfgerr"])? "C":".", \
               (flags & flg["sbit"]["alarm"]) ? "A":".", \
               (flags & flg["sbit"]["trip"])  ? "T":".")
         if (flags & flg["tbit"]["digital"]) {
            type = (flags & flg["tbit"]["output"])? "OB" : "IB"
            flags = flags & flg["bchmask"]
            name = wago__hard2log(wcname, cfgarr[ch], type)
            args =  ""
            val = cfgarr[ch]["value"]? "ON" : "OFF"
         } else {
            local ltype

            type = cfgarr[ch]["type"]
            ltype = (flags & flg["tbit"]["output"])? "OW" : "IW"
            name = wago__hard2log(wcname, cfgarr[ch], ltype)
            flags = flags & flg["wchmask"]
            if (type in TypeTbl) {
               if (type == "IV" || type == "OV") {
                  scale  = wago__log2scale(name)
               } else {
                  if((scale  = TypeTbl[type]["scale"]) == 0) {
                     scale = 1
                  }
               }
               offset = TypeTbl[type]["offset"]
            } else {
               type = "??"
               scale  = 1
               offset = 0
            }
            args = sprintf("Low:%g High:%g", \
                    scale * cfgarr[ch]["loth"] + offset, \
                    scale * cfgarr[ch]["hith"] + offset)
            val = scale * cfgarr[ch]["value"] + offset
         }
         pout = sprintf("      #%2d  %s - %10s  %s ", chan, status, name, type)
         if (args != "")
            pout = sprintf("%s %s ", pout, args)
         if (flags) {
            for (f in flg["cbit"]) {
               if (flags & flg["cbit"][f]) {
                  pout = sprintf("%s %s", pout, f)
                  flags &= ~flg["cbit"][f]
               }
            }
         }
         if (showstat)
            pout = sprintf("%-65s [%s]", pout, val)
         print pout
      }
      print
   }
}'

#%MACROS%
#%SETUP%
# The Wago controllers must be declared in the setup file by means of the
# %B%wagosetup%B% macro (see help in %B%wagocore.mac%B%).
# This requires that the the device server Wagods must be properly configured
# to access to the Wago controllers.%BR%
# The interlock configuration file uses the same logical names attributed in
# the device server configuration to the different input/output channels of
# the controllers.
#
#%AUTHOR% P.Fajardo, (Original 4/2005).
#  $Revision: 1.8 $ / $Date: 2016/12/12 13:59:33 $
#%TOC%