--- a/ConfigTreeNode.py Wed May 13 22:25:22 2020 +0200
+++ b/ConfigTreeNode.py Thu May 14 09:24:09 2020 +0200
@@ -678,6 +678,7 @@
raise UserAddressedException(message)
+# Exception type for problems that user has to take action in order to fix
class UserAddressedException(Exception):
pass
--- a/POULibrary.py Wed May 13 22:25:22 2020 +0200
+++ b/POULibrary.py Thu May 14 09:24:09 2020 +0200
@@ -26,6 +26,7 @@
from __future__ import absolute_import
from weakref import ref
+from ConfigTreeNode import UserAddressedException
class POULibrary(object):
def __init__(self, CTR, LibName, TypeStack):
@@ -59,6 +60,11 @@
# Pure python or IEC libs doesn't produce C code
return ((""), [], False), ""
+ def FatalError(self, message):
+ """ Raise an exception that will trigger error message intended to
+ the user, but without backtrace since it is not a software error """
+
+ raise UserAddressedException(message)
def SimplePOULibraryFactory(path):
class SimplePOULibrary(POULibrary):
--- a/svghmi/svghmi.py Wed May 13 22:25:22 2020 +0200
+++ b/svghmi/svghmi.py Thu May 14 09:24:09 2020 +0200
@@ -70,6 +70,7 @@
def place_node(self, node):
best_child = None
known_best_match = 0
+ potential_siblings = {}
for child in self.children:
if child.path is not None:
in_common = 0
@@ -86,10 +87,25 @@
in_common == len(child.path) - 1:
known_best_match = in_common
best_child = child
+ else:
+ potential_siblings[child.path[
+ -2 if child.nodetype == "HMI_NODE" else -1]] = child
if best_child is not None:
- best_child.place_node(node)
+ if node.nodetype == "HMI_NODE" and best_child.path[:-1] == node.path[:-1]:
+ return "Duplicate_HMI_NODE", best_child
+ return best_child.place_node(node)
else:
+ candidate_name = node.path[-2 if node.nodetype == "HMI_NODE" else -1]
+ if candidate_name in potential_siblings:
+ return "Non_Unique", potential_siblings[candidate_name]
+
+ if node.nodetype == "HMI_NODE" and len(self.children) > 0:
+ prev = self.children[-1]
+ if prev.path[:-1] == node.path[:-1]:
+ return "Late_HMI_NODE",prev
+
self.children.append(node)
+ return None
def etree(self, add_hash=False):
@@ -195,7 +211,7 @@
for i,v in enumerate(hmi_types_instances):
path = v["IEC_path"].split(".")
derived = v["derived"]
- if derived == "HMI_NODE" and ['CONFIG', 'HEARTBEAT'] :
+ if derived == "HMI_NODE":
hmi_tree_root = HMITreeNode(path, "", derived, v["type"], v["vartype"], v["C_path"])
hmi_types_instances.pop(i)
break
@@ -217,7 +233,32 @@
else:
name = path[-1]
new_node = HMITreeNode(path, name, derived, v["type"], v["vartype"], v["C_path"], **kwargs)
- hmi_tree_root.place_node(new_node)
+ placement_result = hmi_tree_root.place_node(new_node)
+ if placement_result is not None:
+ cause, problematic_node = placement_result
+ if cause == "Non_Unique":
+ message = _("HMI tree nodes paths are not unique.\nConflicting variable: {} {}").format(
+ ".".join(problematic_node.path),
+ ".".join(new_node.path))
+
+ last_FB = None
+ for v in varlist:
+ if v["vartype"] == "FB":
+ last_FB = v
+ if v["C_path"] == problematic_node:
+ break
+ if last_FB is not None:
+ failing_parent = last_FB["type"]
+ message += "\n"
+ message += _("Solution: Add HMI_NODE at beginning of {}").format(failing_parent)
+
+ elif cause in ["Late_HMI_NODE", "Duplicate_HMI_NODE"]:
+ cause, problematic_node = placement_result
+ message = _("There must be only one occurrence of HMI_NODE before any HMI_* variable in POU.\nConflicting variable: {} {}").format(
+ ".".join(problematic_node.path),
+ ".".join(new_node.path))
+
+ self.FatalError("SVGHMI : " + message)
if on_hmitree_update is not None:
on_hmitree_update()