Commit 9c70aec2 authored by Antonia Beteva's avatar Antonia Beteva

Merge branch 'wago_non_standard_modules' into 'master'

Wago non standard modules

See merge request !1969
parents 63e20065 1beb8654
Pipeline #21125 failed with stages
in 4 minutes and 26 seconds
......@@ -8,6 +8,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- Wago:
- support for special modules `750-403`, `750-506`, `750-507`, `750-508`, `750-637`
- create an "extended_mode" (normally not used) to be able to retrieve all
information from special modules (status, both input and output).
Activating this mode gives the capability to access those extra information, but
could be not compatible with actual ISG protocol and with C++ device server.
- `devreadphys` and `devreaddigi` will take values from a cache like is in C++
device server, to get instantaneous values use `devreadnocachephys` and
`devreadnocachedigi`. This can be used for monitoring without giving more
pressure to the Wago. The shell command `get` gives instantaneous values.
- Flint
- A preferred style can be saved as default for images
......
......@@ -221,10 +221,11 @@ def Wago(
)
isinput = bool(module_info[0] or module_info[2])
isoutput = bool(module_info[1] or module_info[3])
if isinput and isoutput or not isinput and not isoutput:
raise RuntimeError(
"Wago couldn't have both input and output I/O in the same module"
)
if isinput and isoutput:
# special modules are used as out modules
# because reading input registers will give you the status
# and reading output will give real values
isinput = False
size = module_info[4]
if isdigital:
io_info.append(
......
......@@ -39,16 +39,16 @@ def remove_comments(iterable):
yield l
def to_signed(num: int) -> int:
def to_signed(num: int, bits=16) -> int:
"""convert a 16 bit number to a signed representation"""
if num >> 15: # if is negative
calc = -((num ^ 0xffff) + 1) # 2 complement
if num >> (bits - 1): # if is negative
calc = -((num ^ ((1 << bits) - 1)) + 1) # 2 complement
return calc
return num
def to_unsigned(num: int) -> int:
return num & ((1 << 16) - 1)
def to_unsigned(num: int, bits=16) -> int:
return num & ((1 << bits) - 1)
def word_to_2ch(in_: int) -> bytes:
......
......@@ -395,13 +395,6 @@ def beacon_interlock_parsing(yml, modules_config: ModulesConfig):
type_flags |= FLAGS["tbit"]["digital"]
chflags = string_to_flags(channel.get("flags", "")) | type_flags
"""
if channel["type"] == "TC":
scale = 10
min_ = channel["min"] * scale
max_ = channel["max"] * scale
# continue with all others
"""
parsed = ParsedChannelLine(
channel["logical_name"],
......@@ -637,8 +630,8 @@ def _interlock_channel_info_from_plc(
info["type"]["register_type"] = "IB"
if len(get_conf_output) > 3:
info["low_limit"] = to_signed(get_conf_output[2])
info["high_limit"] = to_signed(get_conf_output[3])
info["low_limit"] = to_unsigned(get_conf_output[2], bits=16)
info["high_limit"] = to_unsigned(get_conf_output[3], bits=16)
info["type"]["type"] = word_to_2ch(get_conf_output[-2])
if len(get_conf_output) > 6: # TODO: check order of a dac
info["dac"] = get_conf_output[5]
......@@ -655,18 +648,21 @@ def _interlock_channel_info_from_plc(
info["logical_device_channel"] = logical_device_channel
# scale information
_, _, module_type, _, _ = modules_config.logical_mapping[logical_device][
logical_device_channel
module_type = modules_config.read_table[logical_device][logical_device_channel][
"module_reference"
]
type_ = get_module_info(module_type).reading_type
if module_type == "fs30":
info["type"]["scale"] = 30
if info["type"]["type"] in ("IW", "OW"):
# do not scale because we ask for raw values
info["type"]["scale"] = 1
elif module_type == "fs30":
info["type"]["scale"] = 30 # check this
elif type_ == "thc":
info["type"]["scale"] = 10
elif type_.startswith("fc"):
info["type"]["scale"] = 10 / 0x8000
elif type_.startswith("fs"):
info["type"]["scale"] = 0x8000 / 10
return info
......@@ -735,25 +731,36 @@ def _interlock_channel_info_from_parsed(num, parsed, modules_config: ModulesConf
info["type"]["register_type"] = "IB"
# scale information
_, _, module_type, _, _ = modules_config.logical_mapping[parsed.logical_device][
module_type = modules_config.read_table[parsed.logical_device][
parsed.logical_device_channel
]
]["module_reference"]
type_ = get_module_info(module_type).reading_type
if module_type == "fs30":
if parsed.type in ("IW", "OW"):
# do not scale because we ask for raw values
scale = 1
elif module_type == "fs30":
scale = 30
elif type_ == "thc":
scale = 10
elif type_.startswith("fc"):
scale = 10 / 0x8000
elif type_.startswith("fs"):
scale = 0x8000 / 10
else:
scale = 1
info["type"]["scale"] = scale
if info["type"]["register_type"] in ("OW", "IW"):
if info["type"]["type"] in ("TC", "IV", "OV"):
# if we have those we should apply a conversion
# converting to raw_values raw_value = (value * scale)
info["low_limit"] = to_unsigned(int(parsed.low_limit * scale))
info["high_limit"] = to_unsigned(int(parsed.high_limit * scale))
elif info["type"]["type"] in ("OW", "IW"):
# if we have those we do not appy conversion
info["low_limit"] = to_unsigned(int(parsed.low_limit))
info["high_limit"] = to_unsigned(int(parsed.high_limit))
"""
# Not implemented parsing
if is_monitor(parsed.flags):
......@@ -823,20 +830,22 @@ def interlock_compare(int_list_1, int_list_2):
for ck in ch_keys:
val1, val2 = ch1[ck], ch2[ck]
if val1 != val2:
if isinstance(val1, (int, float)):
val1 = to_signed(val1)
scale1 = ch1["type"]["scale"]
try:
val1 = val1 / scale1 # better format
except Exception:
pass
if isinstance(val2, (int, float)):
val2 = to_signed(val2)
scale2 = ch2["type"]["scale"]
try:
val2 = val2 / scale2
except Exception:
pass
# apply the scale to some values
if ck in ("low_limit", "high_limit"): # ,"dac", "dac_scale"):
if isinstance(val1, (int, float)):
val1 = to_signed(val1)
scale1 = ch1["type"]["scale"]
try:
val1 = val1 / scale1 # better format
except Exception:
pass
if isinstance(val2, (int, float)):
val2 = to_signed(val2)
scale2 = ch2["type"]["scale"]
try:
val2 = val2 / scale2
except Exception:
pass
messages.append(
f"Interlock n.{num} channel n.{ch_num} for {ck}: {val1} != {val2}"
......@@ -1078,10 +1087,13 @@ def interlock_show(wc_name: str, interlock_info: list):
ch_tripped = "T" if ch["status"]["tripped"] else "."
logical_device = ch["logical_device"]
type_ = ch["type"]["type"]
low_limit = ch["low_limit"]
high_limit = ch["high_limit"]
scale = ch["type"]["scale"]
ch_value = ch["value"] / scale if ch["value"] else None
if type_ in ("IW", "OW"):
# print raw values
ch_value = ch["value"] if ch["value"] else None
else:
ch_value = to_signed(ch["value"]) / scale if ch["value"] else None
ch_logical_device = ch["logical_device"]
chline = f" #{ch_num:>2} {ch_hwerr}{ch_cfgerr}{ch_alarm}{ch_tripped} - {logical_device} {type_} "
......@@ -1092,7 +1104,13 @@ def interlock_show(wc_name: str, interlock_info: list):
else:
ch_value = "ON" if ch["value"] else "OFF"
else:
chline += f"Low:{to_signed(low_limit)/scale:.4f} High:{to_signed(high_limit)/scale:.4f}"
low_limit = to_signed(ch["low_limit"]) / scale
high_limit = to_signed(ch["high_limit"]) / scale
if type_ in ("IW", "OW"):
# print raw values
chline += f"Low:{low_limit:.0f} High:{high_limit:.0f}"
else:
chline += f"Low:{low_limit:.4f} High:{high_limit:.4f}"
chline += " STICKY" if ch["configuration"]["sticky"] else ""
chline += " INVERTED" if ch["configuration"]["inverted"] else ""
......
This diff is collapsed.
......@@ -14,12 +14,13 @@ import sys
import time
import numpy
import bliss.common.tango as tango
import tango
from tango import DebugIt, DevState, Attr, SpectrumAttr
from tango.server import Device, device_property, attribute, command
from bliss.comm.util import get_comm
from bliss.controllers.wago.wago import *
from bliss.controllers.wago.wago import ModulesConfig, WagoController
from bliss.common.utils import flatten
from bliss.config.static import get_config
......@@ -62,6 +63,9 @@ class Wago(Device):
config = device_property(
dtype=tango.DevVarCharArray, default_value="", doc="I/O modules attached to PLC"
)
polling_time = device_property(
dtype=int, default_value=2000, doc="polling time interval in ms"
)
# modbusDevName = device_property(dtype=str, default_value="") # its a link
# __SubDevices = device_property(dtype=str, default_value="") # its a link
......@@ -89,7 +93,11 @@ class Wago(Device):
raise RuntimeError(
"modbustcp url should be given in Beacon configuration"
)
self.config = ModulesConfig.from_config_tree(yml_config).mapping_str
modules_config = ModulesConfig.from_config_tree(yml_config)
self.config = modules_config.mapping_str
self.extended_mode = modules_config.extended_mode
else:
self.extended_mode = False
self.TurnOn() # automatic turn on to mimic C++ Device Server
......@@ -107,7 +115,9 @@ class Wago(Device):
try:
self.set_state(DevState.INIT)
self.debug_stream("Setting Wago modules mapping")
modules_config = ModulesConfig(self.config, ignore_missing=True)
modules_config = ModulesConfig(
self.config, ignore_missing=True, extended_mode=self.extended_mode
)
except Exception as exc:
self.error_stream(f"Exception on Wago setting modules mapping: {exc}")
self.set_state(DevState.FAULT)
......@@ -189,7 +199,11 @@ class Wago(Device):
f"tango attribute {tango_attribute.get_data_format()} {tango_attribute.get_data_size()}"
)
name = tango_attribute.get_name()
value = self.DevReadPhys(self.DevName2Key(name))
value = self.DevReadNoCachePhys(self.DevName2Key(name))
# getting information about the channel, can be used for later postelaboration
# for chann, val in enumerate(value):
# info = self.__info[(name, chann)]
# print(f"{name} {chann} {info}")
if len(value) == 1:
# single value
tango_attribute.set_value(value[0])
......@@ -218,61 +232,49 @@ class Wago(Device):
Creates dynamic attributes from device_property 'config'
"""
attrs = {}
self.__info = {}
# couple of logical_device/logical_channel
device_channels = []
for logical_device in self.wago.logical_keys:
for logical_channel in self.wago.modules_config.read_table[logical_device]:
device_channels.append((logical_device, logical_channel))
for logical_device, logical_channel in device_channels:
for (
key,
(logical_device, physical_channel, physical_module, module_type, _, _),
) in self.wago.physical_mapping.items():
if logical_device not in attrs:
attrs[logical_device] = {}
attrs[logical_device]["size"] = 1
attrs[logical_device] = {"size": 1}
else:
attrs[logical_device]["size"] += 1
for (
key,
(logical_device, physical_channel, physical_module, module_type, _, _),
) in self.wago.physical_mapping.items():
# find the type of attribute
type_ = MODULES_CONFIG[module_type][READING_TYPE]
config = self.wago.modules_config.read_table[logical_device][
logical_channel
]
# determination of variable type
# saving information about the device/channel for later use
info = config["info"].reading_info
self.__info[(logical_device, logical_channel)] = info
if type_ in ("thc", "fs10", "fs20", "fs4-20"):
# determination of variable type
bits = info["bits"]
if bits in (24, 32):
# encoder requires Long
var_type = tango.DevDouble
elif bits == 16:
# temperature and Analog requires Float
var_type = tango.DevDouble
elif type_ in ("ssi24", "ssi32", "637"):
# encoder requires Long
var_type = tango.DevLong
elif type_ in ("digital"):
elif bits == 1:
# digital requires boolean
var_type = tango.DevBoolean
else:
raise NotImplementedError
module_info = MODULES_CONFIG[module_type]
# determination of read/write type
if type_ in ("thc",):
type_ = info["type"]
if type_ in ("DIGI_IN", "ANA_IN"):
read_write = "r"
elif type_ in ("thc", "fs10", "fs20", "fs4-20"):
if module_info[ANA_IN] == module_info[N_CHANNELS]:
read_write = "r"
elif module_info[ANA_OUT] == module_info[N_CHANNELS]:
read_write = "rw"
else:
raise NotImplementedError
elif type_ in ("ssi24", "ssi32", "637"):
read_write = "r"
elif type_ in ("digital"):
if module_info[DIGI_IN] == module_info[N_CHANNELS]:
read_write = "r"
elif module_info[DIGI_OUT] == module_info[N_CHANNELS]:
read_write = "rw"
else:
raise NotImplementedError(
f"Digital I/O number of channels should be equal to total for {module_type}"
)
elif type_ in ("DIGI_OUT", "ANA_OUT"):
read_write = "rw"
else:
raise NotImplementedError
......@@ -401,13 +403,19 @@ class Wago(Device):
dtype_in=tango.DevShort,
doc_in="Logical device",
dtype_out=tango.DevVarShortArray,
doc_out="Array of bit values",
doc_out="Array of bit values (cached)",
)
@DebugIt()
def DevReadDigi(self, key):
"""
"""
return self.DevReadNoCacheDigi(key)
value = self.wago.devreaddigi(key)
try:
len(value)
except TypeError:
value = [value]
return value
@command(
dtype_in=tango.DevShort,
......@@ -419,7 +427,7 @@ class Wago(Device):
def DevReadNoCacheDigi(self, key):
"""
"""
value = self.wago.get(self.wago.devkey2name(key), convert_values=False)
value = self.wago.devreadnocachedigi(key)
try:
len(value)
......@@ -437,7 +445,7 @@ class Wago(Device):
def DevReadNoCachePhys(self, key):
"""
"""
value = self.wago.get(self.wago.devkey2name(key))
value = self.wago.devreadnocachephys(key)
try:
len(value)
except TypeError:
......@@ -448,13 +456,18 @@ class Wago(Device):
dtype_in=tango.DevShort,
doc_in="Logical device index",
dtype_out=tango.DevVarFloatArray,
doc_out="Array of values",
doc_out="Array of values (cached values)",
)
@DebugIt()
def DevReadPhys(self, key):
"""
"""
return self.DevReadNoCachePhys(key)
value = self.wago.devreadphys(key)
try:
len(value)
except TypeError:
value = [value]
return value
@command(
dtype_in=tango.DevVarShortArray,
......
# Interlock Protocol #
# Interlock Protocol
## What is Interlock Protocol? ##
......@@ -58,7 +58,7 @@ Than specify a list of interlocks.
interlocks:
- relay: intlckf
flags: STICKY
name: Interlock 1
description: Interlock 1
channels:
- logical_name: esTf1
type: TC
......@@ -72,7 +72,7 @@ interlocks:
- relay: intlckf
relay_channel: 2
flags: STICKY
name: Interlock 2
description: Interlock 2
channels:
- logical_name: esTr1
type: TC
......@@ -97,7 +97,7 @@ interlocks:
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.
- name: (**Optional**) is simply an user description of the purpose of the interlock condition.
- description: (**Optional**) is simply an user description of the purpose of the interlock condition.
### Configuration of the channel ###
......
This diff is collapsed.
......@@ -11,22 +11,22 @@ def test_wago_ds(wago_tango_server, default_session):
assert "does match Wago attached modules" in dev_proxy.Status()
assert list(dev_proxy.command_inout("DevGetKeys")) == list(range(0, 20))
assert list(dev_proxy.command_inout("DevGetKeys")) == list(range(0, 23))
dev_proxy.command_inout("DevReadDigi", (0))
dev_proxy.command_inout("DevReadPhys", (10))
dev_proxy.command_inout("DevReadNoCacheDigi", (0))
dev_proxy.command_inout("DevReadNoCachePhys", (10))
dev_proxy.command_inout("DevKey2Name", (16)) == "intlckf2"
# testing reading of values
for key in dev_proxy.command_inout("DevGetKeys"):
channel_name = dev_proxy.command_inout("DevKey2Name", key)
value1 = getattr(dev_proxy, channel_name)
value2 = dev_proxy.command_inout("DevReadPhys", key)
value2 = dev_proxy.command_inout("DevReadNoCachePhys", key)
try:
len(value1)
assert all(i == j for i, j in zip(value1, value2))
except TypeError:
assert value1 == value2
assert pytest.approx(value1, value2[0])
# testing writing of values on digital output
for value in ((True, True), (False, True), (False, False)):
......@@ -36,7 +36,7 @@ def test_wago_ds(wago_tango_server, default_session):
for value in (True, False, True):
dev_proxy.intlckf1 = value # writing a logical channel with 1 value
assert dev_proxy.command_inout("DevReadPhys", 15) == value
assert dev_proxy.command_inout("DevReadNoCachePhys", 15) == value
assert dev_proxy.intlckf1 == value
for value in (0, 1, 0):
......@@ -44,7 +44,7 @@ def test_wago_ds(wago_tango_server, default_session):
dev_proxy.command_inout(
"DevWritePhys", [key, 0, value, 1, value, 2, value, 3, value]
)
result = dev_proxy.command_inout("DevReadPhys", key)
result = dev_proxy.command_inout("DevReadNoCachePhys", key)
expected = [value] * 4
assert all(i == j for i, j in zip(result, expected))
......@@ -53,7 +53,18 @@ def test_wago_ds(wago_tango_server, default_session):
dev_proxy.command_inout("DevWritePhys", [1, 0, 0])
for value in (5.3, 7.5, 0.3):
dev_proxy.command_inout("DevWritePhys", [17, 0, value])
dev_proxy.command_inout("DevWritePhys", [20, 0, value])
assert (
pytest.approx(dev_proxy.command_inout("DevReadPhys", (17))[0], .1) == value
pytest.approx(dev_proxy.command_inout("DevReadNoCachePhys", (20))[0], .1)
== value
)
# test cached values
value1 = (False, False)
dev_proxy.double_out = value1
value = dev_proxy.double_out # forcing updating cache
dev_proxy.double_out = (True, True)
# the next will read an old value
key = dev_proxy.command_inout("DevName2Key", "double_out")
value2 = dev_proxy.command_inout("DevReadDigi", (key))
assert all(i == j for i, j in zip(value1, value2))
device:
- tango_name: 1/1/wagodummy
class: Wago
properties:
Iphost: localhost
Protocol: TCP
config:
- 750-504, foh2ctrl, foh2ctrl, foh2ctrl, foh2ctrl
- 750-408, foh2pos, sain2, foh2pos, sain4
- 750-408, foh2pos, sain6, foh2pos, sain8
- 750-408, pres
- 750-469, esTf1, esTf2
- 750-469, esTf3, esTf4
- 750-469, esTr1, esTr2
- 750-469, esTr3, esTr4
- 750-517, intlckf1, intlckf2
- 750-554, o10v1, o10v2
- 750-517, double_out, double_out
TCPTimeout: 1000
personal_name: wago_tg_server
server: Wago
- device:
- tango_name: 1/1/wagodummy
class: Wago
properties:
Iphost: localhost
Protocol: TCP
config:
- 750-504, foh2ctrl, foh2ctrl, foh2ctrl, foh2ctrl
- 750-408, foh2pos, sain2, foh2pos, sain4
- 750-408, foh2pos, sain6, foh2pos, sain8
- 750-408, pres
- 750-469, esTf1, esTf2
- 750-469, esTf3, esTf4
- 750-469, esTr1, esTr2
- 750-469, esTr3, esTr4
- 750-517, intlckf1, intlckf2
- 750-630, encoder1
- 750-508, special_out_1, special_out_2
- 750-554, o10v1, o10v2
- 750-517, double_out, double_out
TCPTimeout: 1000
personal_name: wago_tg_server
server: Wago
......@@ -24,6 +24,10 @@
logical_names: esTr3, esTr4
- type: 750-517
logical_names: intlckf1, intlckf2
- type: 750-630
logical_names: encoder1
- type: 750-508
logical_names: special_out_1, special_out_2
- type: 750-554
logical_names: o10v1, o10v2
- type: 750-517
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment