#%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%
|