Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions BlockServer/config/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
# along with this program; if not, you can obtain a copy from
# https://www.eclipse.org/org/documents/epl-v10.php or
# http://opensource.org/licenses/eclipse-1.0.php
from typing import Dict, TypedDict, Union

from typing import TypedDict

from server_common.helpers import PVPREFIX_MACRO

Expand All @@ -33,6 +34,11 @@ class Block:
log_periodic (bool): Whether the block is sampled periodically in the archiver
log_rate (float): Time between archive samples (in seconds)
log_deadband (float): Deadband for the block to be archived
alarm_enabled (bool): Whether the alarm should be enabled
alarm_latched (bool): Whether the alarm should be latched
alarm_delay (float): The delay for triggering alarm
alarm_guidance (string): The guidance for the alarm

"""

def __init__(
Expand All @@ -51,6 +57,10 @@ def __init__(
log_deadband: float = 0,
set_block: bool = False,
set_block_val: str | None = None,
alarm_enabled: bool = False,
alarm_latched: bool = False,
alarm_delay: float | None = None,
alarm_guidance: str | None = None,
) -> None:
"""Constructor.

Expand All @@ -69,6 +79,10 @@ def __init__(
log_deadband: Deadband for the block to be archived
set_block: whether the block should be set upon config change
set_block_val: what the block should be set to upon config change
alarm_enabled: Whether the alarm should be enabled
alarm_latched: Whether the alarm should be latched
alarm_delay: The delay for triggering alarm
alarm_guidance: The guidance for the alarm
"""
self.name = name
self.pv = pv
Expand All @@ -84,6 +98,10 @@ def __init__(
self.log_deadband = log_deadband
self.set_block = set_block
self.set_block_val = set_block_val
self.alarm_enabled = alarm_enabled
self.alarm_latched = alarm_latched
self.alarm_delay = alarm_delay
self.alarm_guidance = alarm_guidance

def _get_pv(self) -> str:
pv_name = self.pv
Expand Down Expand Up @@ -111,7 +129,7 @@ def __str__(self) -> str:
f"RCHigh: {self.rc_highlimit}{set_block_str}"
)

def to_dict(self) -> Dict[str, Union[str, float, bool, None]]:
def to_dict(self) -> dict[str, str | float | bool | None]:
"""Puts the block's details into a dictionary.

Returns:
Expand All @@ -132,6 +150,10 @@ def to_dict(self) -> Dict[str, Union[str, float, bool, None]]:
"suspend_on_invalid": self.rc_suspend_on_invalid,
"set_block": self.set_block,
"set_block_val": self.set_block_val,
"alarm_enabled": self.alarm_enabled,
"alarm_latched": self.alarm_latched,
"alarm_delay": self.alarm_delay,
"alarm_guidance": self.alarm_guidance,
}


Expand All @@ -147,3 +169,7 @@ class BlockKwargs(TypedDict, total=False):
log_deadband: float
set_block: bool
set_block_val: str | None
alarm_enabled: bool | None
alarm_latched: bool | None
alarm_delay: float | None
alarm_guidance: str | None
75 changes: 56 additions & 19 deletions BlockServer/config/xml_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
# along with this program; if not, you can obtain a copy from
# https://www.eclipse.org/org/documents/epl-v10.php or
# http://opensource.org/licenses/eclipse-1.0.php
from typing import Dict, List, OrderedDict

from collections import OrderedDict
from xml.dom import minidom
from xml.etree import ElementTree

Expand All @@ -27,6 +28,10 @@
from BlockServer.core.constants import (
GRP_NONE,
SIMLEVELS,
TAG_ALARM_DELAY,
TAG_ALARM_ENABLED,
TAG_ALARM_GUIDANCE,
TAG_ALARM_LATCHED,
TAG_AUTOSTART,
TAG_BLOCK,
TAG_BLOCKS,
Expand Down Expand Up @@ -102,7 +107,7 @@ class ConfigurationXmlConverter:
"""

@staticmethod
def blocks_to_xml(blocks: OrderedDict, macros: Dict) -> str:
def blocks_to_xml(blocks: OrderedDict, macros: dict) -> str:
"""Generates an XML representation for a supplied dictionary of blocks.

Args:
Expand All @@ -116,7 +121,7 @@ def blocks_to_xml(blocks: OrderedDict, macros: Dict) -> str:
root.attrib["xmlns"] = SCHEMA_PATH + BLOCK_SCHEMA
root.attrib["xmlns:blk"] = SCHEMA_PATH + BLOCK_SCHEMA
root.attrib["xmlns:xi"] = "http://www.w3.org/2001/XInclude"
for name, block in blocks.items():
for block in blocks.values():
# Don't save if in component
if block.component is None or block.component is False:
ConfigurationXmlConverter._block_to_xml(root, block, macros)
Expand Down Expand Up @@ -144,7 +149,7 @@ def groups_to_xml(groups: OrderedDict, include_none: bool = False) -> str:
ConfigurationXmlConverter._group_to_xml(root, group)

# If we are adding the None group it should go at the end
if include_none and KEY_NONE in groups.keys():
if include_none and KEY_NONE in groups:
ConfigurationXmlConverter._group_to_xml(root, groups[KEY_NONE])
return minidom.parseString(ElementTree.tostring(root)).toprettyxml()

Expand All @@ -162,7 +167,7 @@ def iocs_to_xml(iocs: OrderedDict) -> str:
root.attrib["xmlns"] = SCHEMA_PATH + IOC_SCHEMA
root.attrib["xmlns:ioc"] = SCHEMA_PATH + IOC_SCHEMA
root.attrib["xmlns:xi"] = "http://www.w3.org/2001/XInclude"
for name in iocs.keys():
for name in iocs:
# Don't save if in component
if iocs[name].component is None:
ConfigurationXmlConverter._ioc_to_xml(root, iocs[name])
Expand All @@ -182,7 +187,7 @@ def components_to_xml(comps: OrderedDict) -> str:
root.attrib["xmlns"] = SCHEMA_PATH + COMPONENT_SCHEMA
root.attrib["xmlns:comp"] = SCHEMA_PATH + COMPONENT_SCHEMA
root.attrib["xmlns:xi"] = "http://www.w3.org/2001/XInclude"
for name, case_sensitve_name in comps.items():
for case_sensitve_name in comps.values():
ConfigurationXmlConverter._component_to_xml(root, case_sensitve_name)
return minidom.parseString(ElementTree.tostring(root)).toprettyxml()

Expand Down Expand Up @@ -223,7 +228,7 @@ def meta_to_xml(data: MetaData) -> str:
return minidom.parseString(ElementTree.tostring(root)).toprettyxml()

@staticmethod
def _block_to_xml(root_xml: ElementTree.Element, block: Block, macros: Dict) -> None:
def _block_to_xml(root_xml: ElementTree.Element, block: Block, macros: dict) -> None:
"""Generates the XML for a block"""
name = block.name
read_pv = block.pv
Expand Down Expand Up @@ -276,6 +281,17 @@ def _block_to_xml(root_xml: ElementTree.Element, block: Block, macros: Dict) ->
set_block_val = ElementTree.SubElement(block_xml, TAG_SET_BLOCK_VAL)
set_block_val.text = str(block.set_block_val)

# Alarm Config
alarm_enabled = ElementTree.SubElement(block_xml, TAG_ALARM_ENABLED)
alarm_enabled.text = str(block.alarm_enabled)
alarm_latched = ElementTree.SubElement(block_xml, TAG_ALARM_LATCHED)
alarm_latched.text = str(block.alarm_latched)
if block.alarm_delay is not None:
alarm_delay = ElementTree.SubElement(block_xml, TAG_ALARM_DELAY)
alarm_delay.text = str(block.alarm_delay)
alarm_guidance = ElementTree.SubElement(block_xml, TAG_ALARM_GUIDANCE)
alarm_guidance.text = block.alarm_guidance

@staticmethod
def _group_to_xml(root_xml: ElementTree.Element, group: Group) -> None:
"""Generates the XML for a group"""
Expand Down Expand Up @@ -414,6 +430,28 @@ def blocks_from_xml(
if set_block_val is not None:
blocks[name.lower()].set_block_val = set_block_val.text

# Alarm Config
alarm_enabled = ConfigurationXmlConverter._find_single_node(
b, NS_TAG_BLOCK, TAG_ALARM_ENABLED
)
if alarm_enabled is not None:
blocks[name.lower()].alarm_enabled = alarm_enabled.text == "True"
alarm_latched = ConfigurationXmlConverter._find_single_node(
b, NS_TAG_BLOCK, TAG_ALARM_LATCHED
)
if alarm_latched is not None:
blocks[name.lower()].alarmlatched = alarm_latched.text == "True"
alarm_delay = ConfigurationXmlConverter._find_single_node(
b, NS_TAG_BLOCK, TAG_ALARM_DELAY
)
if alarm_delay is not None and alarm_delay.text is not None:
blocks[name.lower()].alarm_delay = float(alarm_delay.text)
alarm_guidance = ConfigurationXmlConverter._find_single_node(
b, NS_TAG_BLOCK, TAG_ALARM_GUIDANCE
)
if alarm_guidance is not None:
blocks[name.lower()].alarm_guidance = alarm_guidance.text

@staticmethod
def groups_from_xml(
root_xml: ElementTree.Element, groups: OrderedDict, blocks: OrderedDict
Expand All @@ -436,7 +474,7 @@ def groups_from_xml(
gname_low = gname.lower()

# Add the group to the dict unless it already exists (i.e. the group is defined twice)
if gname_low not in groups.keys():
if gname_low not in groups:
groups[gname_low] = Group(gname, gcomp)

blks = ConfigurationXmlConverter._find_all_nodes(g, NS_TAG_GROUP, TAG_BLOCK)
Expand All @@ -448,7 +486,7 @@ def groups_from_xml(
# Unlikely, but may be a config was edited by hand...
if name not in groups[gname_low].blocks:
groups[gname_low].blocks.append(name)
if name.lower() in blocks.keys():
if name.lower() in blocks:
blocks[name.lower()].group = gname

# Remove the block from the NONE group
Expand Down Expand Up @@ -506,8 +544,8 @@ def ioc_from_xml(root_xml: ElementTree.Element, iocs: OrderedDict) -> None:
iocs[n.upper()].pvsets[ps.attrib[TAG_NAME]] = {
TAG_ENABLED: parse_boolean(str(ps.attrib[TAG_ENABLED]))
}
except Exception as err:
raise Exception("Tag not found in ioc.xml (" + str(err) + ")")
except (NodeNotPresentError, KeyError, ValueError, SyntaxError) as err:
raise ValueError("Tag not found in ioc.xml (" + str(err) + ")")

@staticmethod
def components_from_xml(root_xml: ElementTree.Element, components: OrderedDict) -> None:
Expand Down Expand Up @@ -578,10 +616,11 @@ def meta_from_xml(root_xml: ElementTree.Element, data: MetaData) -> None:
@staticmethod
def _find_all_nodes(
root: ElementTree.Element, tag: str, name: str
) -> List[ElementTree.Element]:
) -> list[ElementTree.Element]:
"""Finds all the nodes regardless of whether it has a namespace or not.

For example the name space for IOCs is xmlns:ioc="http://epics.isis.rl.ac.uk/schema/iocs/1.0"
For example the name space for IOCs is
xmlns:ioc="http://epics.isis.rl.ac.uk/schema/iocs/1.0"

Args:
root: The XML tree object
Expand Down Expand Up @@ -641,7 +680,7 @@ def _find_single_node_with_none_check(
return node

@staticmethod
def _display(child: ElementTree.Element, index: int) -> Dict[str, str | int | None]:
def _display(child: ElementTree.Element, index: int) -> dict[str, str | int | None]:
return {
"index": index,
"name": ConfigurationXmlConverter._find_single_node_with_none_check(
Expand All @@ -659,7 +698,7 @@ def _display(child: ElementTree.Element, index: int) -> Dict[str, str | int | No
}

@staticmethod
def _button(child: ElementTree.Element, index: int) -> Dict[str, str | int | None]:
def _button(child: ElementTree.Element, index: int) -> dict[str, str | int | None]:
return {
"index": index,
"name": ConfigurationXmlConverter._find_single_node_with_none_check(
Expand Down Expand Up @@ -694,7 +733,7 @@ def _button(child: ElementTree.Element, index: int) -> Dict[str, str | int | Non
@staticmethod
def banner_config_from_xml(
root: ElementTree.Element,
) -> Dict[str, List[Dict[str, str | int | None]]]:
) -> dict[str, list[dict[str, str | int | None]]]:
"""
Parses the banner config XML to produce a banner config dictionary

Expand All @@ -714,16 +753,14 @@ def banner_config_from_xml(
banner_buttons = []

items = ConfigurationXmlConverter._find_single_node_with_none_check(root, "banner", "items")
index = 0

for item in items:
for index, item in enumerate(items):
child = item.find("./")
if child is not None:
if "display" in child.tag:
banner_displays.append(ConfigurationXmlConverter._display(child, index))
else:
banner_buttons.append(ConfigurationXmlConverter._button(child, index))
index += 1

return {
"items": banner_displays,
Expand Down
6 changes: 6 additions & 0 deletions BlockServer/core/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,9 @@
FILENAME_GLOBALS = "globals.txt"

SCHEMA_FOR = [FILENAME_BLOCKS, FILENAME_GROUPS, FILENAME_IOCS, FILENAME_COMPONENTS, FILENAME_META]

# Alarm element nodes
TAG_ALARM_ENABLED = "alarm_enabled"
TAG_ALARM_LATCHED = "alarm_latched"
TAG_ALARM_DELAY = "alarm_delay"
TAG_ALARM_GUIDANCE = "alarm_guidance"
15 changes: 14 additions & 1 deletion BlockServer/test_modules/test_configuration_xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@
from collections import OrderedDict
from xml.etree import ElementTree

from server_common.helpers import MACROS

from BlockServer.config.block import Block
from BlockServer.config.configuration import Configuration
from BlockServer.config.group import Group
from BlockServer.config.ioc import IOC
from BlockServer.config.metadata import MetaData
from BlockServer.config.xml_converter import ConfigurationXmlConverter
from server_common.helpers import MACROS

BLOCKS_XML = """
<?xml version="1.0" ?>
Expand All @@ -44,6 +45,9 @@
<log_deadband>0</log_deadband>
<set_block>False</set_block>
<set_block_val>None</set_block_val>
<alarm_enabled>False</alarm_enabled>
Comment thread
Chsudeepta marked this conversation as resolved.
<alarm_latched>False</alarm_latched>
<alarm_guidance/>
</block>
<block>
<name>TESTBLOCK2</name>
Expand All @@ -59,6 +63,9 @@
<log_deadband>0</log_deadband>
<set_block>False</set_block>
<set_block_val>None</set_block_val>
<alarm_enabled>False</alarm_enabled>
<alarm_latched>False</alarm_latched>
<alarm_guidance/>
</block>
<block>
<name>TESTBLOCK3</name>
Expand All @@ -74,6 +81,9 @@
<log_deadband>0</log_deadband>
<set_block>False</set_block>
<set_block_val>None</set_block_val>
<alarm_enabled>False</alarm_enabled>
<alarm_latched>False</alarm_latched>
<alarm_guidance/>
</block>
<block>
<name>TESTBLOCK4</name>
Expand All @@ -89,6 +99,9 @@
<log_deadband>0</log_deadband>
<set_block>False</set_block>
<set_block_val>None</set_block_val>
<alarm_enabled>False</alarm_enabled>
<alarm_latched>False</alarm_latched>
<alarm_guidance/>
</block>
</blocks>"""

Expand Down
Loading