Skip to content
Snippets Groups Projects
Commit 36c5888f authored by Corné Lukken's avatar Corné Lukken
Browse files

L2SS-1986: Process new requirements and fix bugs

parent 24d6eccc
No related branches found
No related tags found
1 merge request!980L2SS-1986: Process new requirements and fix bugs
......@@ -151,6 +151,7 @@ Next change the version in the following places:
# Release Notes
* 0.42.5 Add additional features to protection control
* 0.42.4 Add integration test fixture that routinely tests against cross test dependencies
* 0.42.3 Use PyTango 10.0.0rc3 to reduce memory leaks
* 0.42.2 Add protection control device shutting down station during over temperature
......
0.42.4
0.42.5
......@@ -8,7 +8,7 @@ from typing import List
import timeout_decorator
from tango import Database, DevState
from tangostationcontrol.devices import UNB2, SDPFirmware, RECVL, RECVH, APSCT
from tangostationcontrol.devices import UNB2, SDPFirmware, RECVL, RECVH, APSCT, APSPU
from integration_test.default.devices.base import TestDeviceBase
......@@ -30,6 +30,7 @@ class TestDeviceProtectionControl(TestDeviceBase):
recvl_devices = db.get_device_exported_for_class(RECVL.__name__)
recvh_devices = db.get_device_exported_for_class(RECVH.__name__)
apsct_devices = db.get_device_exported_for_class(APSCT.__name__)
apspu_devices = db.get_device_exported_for_class(APSPU.__name__)
self.setup_proxies(unb2_devices)
self.setup_proxies(sdpfirmware_devices)
......@@ -37,6 +38,7 @@ class TestDeviceProtectionControl(TestDeviceBase):
self.setup_proxies(recvh_devices)
self.setup_proxies(unb2_devices)
self.setup_proxies(apsct_devices)
self.setup_proxies(apspu_devices)
super().setUp("stat/protectioncontrol/1")
......
......@@ -43,7 +43,6 @@ from tangostationcontrol.protection.metrics import ProtectionMetrics
from tangostationcontrol.protection.protection_manager import ProtectionManager
from tangostationcontrol.protection.state import ProtectionStateEnum
from tangostationcontrol.protection.threshold import (
NumberProtectionThreshold,
FilteredNumberProtectionThreshold,
)
......@@ -62,26 +61,48 @@ class ProtectionControl(LOFARDevice):
{
"UNB2": CaseInsensitiveDict(
{
"UNB2_FPGA_POL_CORE_TEMP_R": NumberProtectionThreshold(
minimal=0.0, maximum=90.0
"UNB2_FPGA_POL_CORE_TEMP_R": FilteredNumberProtectionThreshold(
minimal=0.0, maximum=90.0, lower_limit=-128.0, upper_limit=350.0
)
}
),
"SDPFirmware": CaseInsensitiveDict(
{
"FPGA_temp_R": FilteredNumberProtectionThreshold(
minimal=0.0, maximum=90.0, lower_limit=-273.0, upper_limit=350.0
minimal=0.0, maximum=90.0, lower_limit=-128.0, upper_limit=350.0
)
}
),
"RECVL": CaseInsensitiveDict(
{"RCU_TEMP_R": NumberProtectionThreshold(minimal=0.0, maximum=90.0)}
{
"RCU_TEMP_R": FilteredNumberProtectionThreshold(
minimal=0.0, maximum=90.0, lower_limit=-128.0, upper_limit=350.0
)
}
),
"RECVH": CaseInsensitiveDict(
{"RCU_TEMP_R": NumberProtectionThreshold(minimal=0.0, maximum=90.0)}
{
"RCU_TEMP_R": FilteredNumberProtectionThreshold(
minimal=0.0, maximum=90.0, lower_limit=-128.0, upper_limit=350.0
)
}
),
"APSCT": CaseInsensitiveDict(
{"APSCT_TEMP_R": NumberProtectionThreshold(minimal=0.0, maximum=90.0)}
{
"APSCT_TEMP_R": FilteredNumberProtectionThreshold(
minimal=0.0, maximum=90.0, lower_limit=-128.0, upper_limit=350.0
)
}
),
"APSPU": CaseInsensitiveDict(
{
"APSPU_RCU2A_TEMP_R": FilteredNumberProtectionThreshold(
minimal=0.0,
maximum=100.0,
lower_limit=-128.0,
upper_limit=350.0,
)
}
),
}
)
......@@ -109,6 +130,8 @@ class ProtectionControl(LOFARDevice):
def state_R(self):
if self._protection_manager:
return self._protection_manager.state
else:
return ProtectionStateEnum.DEACTIVATED
@attribute(
doc="Whether the station is periodically evaluated against damage",
......@@ -166,6 +189,14 @@ class ProtectionControl(LOFARDevice):
pairs.append(f"{device_name}.{attribute_name}")
return pairs
@attribute(
doc="The last device attribute pair change event that was above the threshold,"
"this value can be extremely stale!",
dtype=str,
)
def last_threshold_device_attribute(self):
return self._metrics.threshold_device_attribute
# --------
# Overloaded functions
# --------
......@@ -227,8 +258,13 @@ class ProtectionControl(LOFARDevice):
self, device: DeviceProxy, attribute_name: str, value: Any
):
try:
self._protection_manager.evaluate(
if self._protection_manager.evaluate(
device.info().dev_class, attribute_name, value
):
logger.error(
"Device: %s has attribute: %s that is above threshold",
device.name(),
attribute_name,
)
self._metrics.update_change_event(device.name(), attribute_name)
except Exception:
......
......@@ -20,6 +20,7 @@ class ProtectionMetrics:
"""Collecting and serving metrics about protecting station against damage"""
def __init__(self, mapping: protection_metric_mapping_type):
self._threshold_device_attribute = ""
self._number_of_discovered_devices = 0
self._number_of_connected_devices = 0
self._device_names: List[str] = []
......@@ -47,6 +48,10 @@ class ProtectionMetrics:
def time_last_change_events(self) -> List[int]:
return self._time_last_change_events
@property
def threshold_device_attribute(self) -> str:
return self._threshold_device_attribute
def update_device_metrics(self, proxies: device_proxy_type):
"""Update statistics about device connection metrics"""
......@@ -70,3 +75,6 @@ class ProtectionMetrics:
device_name,
attribute_name,
)
def update_threshold_device_attribute(self, device_name: str, attribute_name: str):
self._threshold_device_attribute = f"{device_name}.{attribute_name}"
......@@ -62,12 +62,13 @@ class ProtectionManager(ICreateProxies):
convert_protection_to_device_config(self._config), self._proxies
)
def evaluate(self, device_class: str, attribute_name: str, value: Any):
def evaluate(self, device_class: str, attribute_name: str, value: Any) -> bool:
"""Evaluate if the attribute value should trigger a protective shutdown"""
if self._config[device_class][attribute_name].evaluate(value):
logger.warning("Device: %s has attribute: %s that is above threshold")
result = self._config[device_class][attribute_name].evaluate(value)
if result:
self.protective_shutdown()
return result
def _transition_minus_one(self, reference_state: StationStateEnum):
try:
......
......@@ -19,12 +19,13 @@ class ProtectionStateEnum(IntEnum):
transitioning -> active
"""
OFF = 0 # No protective shutdown is ongoing
ARMED = 1 # A protective shutdown is being prepared
ACTIVE = 2 # A protective shutdown is ongoing and the stationmanager is locked against state transitions
TRANSITIONING = 3 # Protective shutdown transitions are taking place, the shutdown is ongoing the stationmanager is locked
FAULT = 4 # The protective shutdown encountered an unrecoverable exception
ABORTED = 5 # If the protective shutdown was aborted before completion
DEACTIVATED = 0 # protective control measures are disabled outright
OFF = 1 # No protective shutdown is ongoing
ARMED = 2 # A protective shutdown is being prepared
ACTIVE = 3 # A protective shutdown is ongoing and the stationmanager is locked against state transitions
TRANSITIONING = 4 # Protective shutdown transitions are taking place, the shutdown is ongoing the stationmanager is locked
FAULT = 5 # The protective shutdown encountered an unrecoverable exception
ABORTED = 6 # If the protective shutdown was aborted before completion
""""""
......
......@@ -30,12 +30,12 @@ class NumberProtectionThreshold(IProtectionThreshold):
:return: returns true if any value is above or below threshold
"""
if isinstance(value, numpy.ndarray):
return numpy.any(value > maximum) or numpy.any(value < minimal)
return numpy.any(value > maximum) # or numpy.any(value < minimal)
elif sequence_not_str(value) and len(value) > 0:
for element in value:
if NumberProtectionThreshold._evaluate(element, minimal, maximum):
return True
elif value > maximum or value < minimal:
elif value > maximum: # or value < minimal:
return True
return False
......
......@@ -23,8 +23,8 @@ class TestProtectionThreshold(base.TestCase):
test = NumberProtectionThreshold(minimal=0, maximum=90.0)
self.assertTrue(test.evaluate(-1))
self.assertTrue(test.evaluate(-0.1))
# self.assertTrue(test.evaluate(-1))
# self.assertTrue(test.evaluate(-0.1))
self.assertTrue(test.evaluate(91))
self.assertTrue(test.evaluate(90.1))
......@@ -35,10 +35,10 @@ class TestProtectionThreshold(base.TestCase):
def test_number_threshold_list(self):
test = NumberProtectionThreshold(minimal=0, maximum=90.0)
self.assertTrue(
test.evaluate([0, 0, 0, 0, -1]),
"Single dimensionsal list below threshold failed",
)
# self.assertTrue(
# test.evaluate([0, 0, 0, 0, -1]),
# "Single dimensionsal list below threshold failed",
# )
self.assertTrue(
test.evaluate(
[[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 91, 0, 0], [0, 0, 0, 0, 0]]
......@@ -60,18 +60,18 @@ class TestProtectionThreshold(base.TestCase):
def test_number_threshold_tuple(self):
test = NumberProtectionThreshold(minimal=0, maximum=90.0)
self.assertTrue(
test.evaluate(
(
0,
0,
0,
0,
-1,
)
),
"Single dimensionsal tuple below threshold failed",
)
# self.assertTrue(
# test.evaluate(
# (
# 0,
# 0,
# 0,
# 0,
# -1,
# )
# ),
# "Single dimensionsal tuple below threshold failed",
# )
self.assertTrue(
test.evaluate(
(
......@@ -191,9 +191,9 @@ class TestProtectionThreshold(base.TestCase):
minimal=StationStateEnum.HIBERNATE, maximum=StationStateEnum.STANDBY
)
self.assertTrue(
test.evaluate(StationStateEnum.OFF), "Enum scalar below threshold failed"
)
# self.assertTrue(
# test.evaluate(StationStateEnum.OFF), "Enum scalar below threshold failed"
# )
self.assertTrue(
test.evaluate(StationStateEnum.ON), "Enum scalar above threshold failed"
......@@ -204,14 +204,14 @@ class TestProtectionThreshold(base.TestCase):
"Enum scalar within threshold failed",
)
self.assertTrue(
test.evaluate([StationStateEnum.STANDBY] * 3 + [StationStateEnum.OFF]),
"Single dimensionsal list below threshold failed",
)
# self.assertTrue(
# test.evaluate([StationStateEnum.STANDBY] * 3 + [StationStateEnum.OFF]),
# "Single dimensionsal list below threshold failed",
# )
self.assertTrue(
test.evaluate(
[[StationStateEnum.STANDBY] * 3 + [StationStateEnum.OFF]] * 4
),
"Multidimension dimensionsal list below threshold failed",
)
# self.assertTrue(
# test.evaluate(
# [[StationStateEnum.STANDBY] * 3 + [StationStateEnum.OFF]] * 4
# ),
# "Multidimension dimensionsal list below threshold failed",
# )
......@@ -121,4 +121,4 @@ commands =
[flake8]
filename = *.py,.stestr.conf,.txt
ignore = B014, B019, B028, W291, W293, W391, E111, E114, E121, E122, E123, E124, E126, E127, E128, E131, E201, E201, E202, E203, E221, E222, E225, E226, E231, E241, E251, E252, E261, E262, E265, E271, E301, E302, E303, E305, E306, E401, E402, E501, E502, E701, E712, E721, E731, F403, F523, F541, F841, H301, H306, H401, H403, H404, H405, W503
exclude = SNMP_mib_loading,output_pymibs
exclude = SNMP_mib_loading,output_pymibs,_proto
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment