Edouard@2745: #!/usr/bin/env python
Edouard@2745: # -*- coding: utf-8 -*-
Edouard@2745:
Edouard@2745: # This file is part of Beremiz
Edouard@2745: # Copyright (C) 2019: Edouard TISSERANT
Edouard@2745: #
Edouard@2745: # See COPYING file for copyrights details.
Edouard@2745:
Edouard@2745: from __future__ import absolute_import
Edouard@2745: import os
Edouard@2745: import shutil
Edouard@2763: from itertools import izip, imap
Edouard@2757: from pprint import pprint, pformat
Edouard@2789: import hashlib
Edouard@2745:
Edouard@2745: import wx
Edouard@2745:
Edouard@2745: import util.paths as paths
Edouard@2745: from POULibrary import POULibrary
Edouard@2756: from docutil import open_svg, get_inkscape_path
Edouard@2753: from lxml import etree
Edouard@2745:
Edouard@2756: from util.ProcessLogger import ProcessLogger
Edouard@2764: from runtime.typemapping import DebugTypesSize
Edouard@2767: import targets
Edouard@2756:
Edouard@2749: HMI_TYPES_DESC = {
Edouard@2749: "HMI_CLASS":{},
Edouard@2749: "HMI_LABEL":{},
Edouard@2749: "HMI_STRING":{},
Edouard@2749: "HMI_INT":{},
Edouard@2749: "HMI_REAL":{}
Edouard@2749: }
Edouard@2749:
Edouard@2749: HMI_TYPES = HMI_TYPES_DESC.keys()
Edouard@2745:
Edouard@2753: from XSLTransform import XSLTransform
Edouard@2753:
Edouard@2753: ScriptDirectory = paths.AbsDir(__file__)
Edouard@2753:
Edouard@2757: class HMITreeNode(object):
Edouard@2764: def __init__(self, path, name, nodetype, iectype = None, vartype = None):
Edouard@2757: self.path = path
Edouard@2757: self.name = name
Edouard@2757: self.nodetype = nodetype
Edouard@2764:
Edouard@2764: if iectype is not None:
Edouard@2764: self.iectype = iectype
Edouard@2764: self.vartype = vartype
Edouard@2757: if nodetype in ["HMI_LABEL", "HMI_ROOT"]:
Edouard@2757: self.children = []
Edouard@2757:
Edouard@2757: def pprint(self, indent = 0):
Edouard@2757: res = ">"*indent + pformat(self.__dict__, indent = indent, depth = 1) + "\n"
Edouard@2757: if hasattr(self, "children"):
Edouard@2757: res += "\n".join([child.pprint(indent = indent + 1)
Edouard@2757: for child in self.children])
Edouard@2757: res += "\n"
Edouard@2757:
Edouard@2757: return res
Edouard@2757:
Edouard@2757: def place_node(self, node):
Edouard@2757: best_child = None
Edouard@2757: known_best_match = 0
Edouard@2762: for child in self.children :
Edouard@2762: if child.path is not None:
Edouard@2762: in_common = 0
Edouard@2762: for child_path_item, node_path_item in izip(child.path, node.path):
Edouard@2762: if child_path_item == node_path_item:
Edouard@2762: in_common +=1
Edouard@2762: else:
Edouard@2762: break
Edouard@2762: if in_common > known_best_match:
Edouard@2762: known_best_match = in_common
Edouard@2762: best_child = child
Edouard@2757: if best_child is not None and best_child.nodetype == "HMI_LABEL":
Edouard@2757: best_child.place_node(node)
Edouard@2757: else:
Edouard@2757: self.children.append(node)
Edouard@2757:
Edouard@2788: def etree(self, add_hash=False):
Edouard@2763:
Edouard@2763: attribs = dict(name=self.name)
Edouard@2763: if self.path is not None:
Edouard@2788: attribs["path"] = ".".join(self.path)
Edouard@2788:
Edouard@2788: if add_hash:
Edouard@2788: attribs["hash"] = ",".join(map(str,self.hash()))
Edouard@2763:
Edouard@2763: res = etree.Element(self.nodetype, **attribs)
Edouard@2763:
Edouard@2763: if hasattr(self, "children"):
Edouard@2763: for child_etree in imap(lambda c:c.etree(), self.children):
Edouard@2763: res.append(child_etree)
Edouard@2763:
Edouard@2763: return res
Edouard@2763:
Edouard@2764: def traverse(self):
Edouard@2764: yield self
Edouard@2764: if hasattr(self, "children"):
Edouard@2764: for c in self.children:
Edouard@2764: for yoodl in c.traverse():
Edouard@2764: yield yoodl
Edouard@2764:
Edouard@2788:
Edouard@2788: def hash(self):
Edouard@2788: """ Produce a hash, any change in HMI tree structure change that hash """
Edouard@2788: s = hashlib.new('md5')
Edouard@2788: self._hash(s)
Edouard@2788: # limit size to HMI_HASH_SIZE as in svghmi.c
Edouard@2788: return map(ord,s.digest())[:8]
Edouard@2788:
Edouard@2788: def _hash(self, s):
Edouard@2788: s.update(str((self.name,self.nodetype)))
Edouard@2788: if hasattr(self, "children"):
Edouard@2788: for c in self.children:
Edouard@2788: c._hash(s)
Edouard@2788:
Edouard@2763: # module scope for HMITree root
Edouard@2763: # so that CTN can use HMITree deduced in Library
Edouard@2763: # note: this only works because library's Generate_C is
Edouard@2763: # systematicaly invoked before CTN's CTNGenerate_C
Edouard@2763:
Edouard@2763: hmi_tree_root = None
Edouard@2757:
Edouard@2745: class SVGHMILibrary(POULibrary):
Edouard@2745: def GetLibraryPath(self):
Edouard@2750: return paths.AbsNeighbourFile(__file__, "pous.xml")
Edouard@2745:
Edouard@2749: def Generate_C(self, buildpath, varlist, IECCFLAGS):
Edouard@2788: global hmi_tree_root, hmi_tree_unique_id
Edouard@2749:
Edouard@2757: """
Edouard@2757: PLC Instance Tree:
Edouard@2757: prog0
Edouard@2757: +->v1 HMI_INT
Edouard@2757: +->v2 HMI_INT
Edouard@2757: +->fb0 (type mhoo)
Edouard@2757: | +->va HMI_LABEL
Edouard@2757: | +->v3 HMI_INT
Edouard@2757: | +->v4 HMI_INT
Edouard@2757: |
Edouard@2757: +->fb1 (type mhoo)
Edouard@2757: | +->va HMI_LABEL
Edouard@2757: | +->v3 HMI_INT
Edouard@2757: | +->v4 HMI_INT
Edouard@2757: |
Edouard@2757: +->fb2
Edouard@2757: +->v5 HMI_IN
Edouard@2757:
Edouard@2757: HMI tree:
Edouard@2757: hmi0
Edouard@2757: +->v1
Edouard@2757: +->v2
Edouard@2757: +->fb0_va
Edouard@2757: | +-> v3
Edouard@2757: | +-> v4
Edouard@2757: |
Edouard@2757: +->fb1_va
Edouard@2757: | +-> v3
Edouard@2757: | +-> v4
Edouard@2757: |
Edouard@2757: +->v5
Edouard@2757:
Edouard@2757: """
Edouard@2757:
Edouard@2749: # Filter known HMI types
Edouard@2749: hmi_types_instances = [v for v in varlist if v["derived"] in HMI_TYPES]
Edouard@2758:
Edouard@2758: hmi_tree_root = HMITreeNode(None, "/", "HMI_ROOT")
Edouard@2757:
Edouard@2762: # add special nodes
Edouard@2762: map(lambda (n,t): hmi_tree_root.children.append(HMITreeNode(None,n,t)), [
Edouard@2762: ("plc_status", "HMI_PLC_STATUS"),
Edouard@2762: ("current_page", "HMI_CURRENT_PAGE")])
Edouard@2757:
Edouard@2757: # deduce HMI tree from PLC HMI_* instances
Edouard@2757: for v in hmi_types_instances:
Edouard@2764: path = v["C_path"].split(".")
Edouard@2758: # ignores variables starting with _TMP_
Edouard@2758: if path[-1].startswith("_TMP_"):
Edouard@2758: continue
Edouard@2764: new_node = HMITreeNode(path, path[-1], v["derived"], v["type"], v["vartype"])
Edouard@2757: hmi_tree_root.place_node(new_node)
Edouard@2757:
Edouard@2764: variable_decl_array = []
Edouard@2764: extern_variables_declarations = []
Edouard@2765: buf_index = 0
Edouard@2775: item_count = 0
Edouard@2764: for node in hmi_tree_root.traverse():
Edouard@2788: if hasattr(node, "iectype") and \
Edouard@2788: node.nodetype not in ["HMI_CLASS", "HMI_LABEL"]:
Edouard@2764: sz = DebugTypesSize.get(node.iectype, 0)
Edouard@2764: variable_decl_array += [
Edouard@2764: "{&(" + ".".join(node.path) + "), " + node.iectype + {
Edouard@2764: "EXT": "_P_ENUM",
Edouard@2764: "IN": "_P_ENUM",
Edouard@2764: "MEM": "_O_ENUM",
Edouard@2764: "OUT": "_O_ENUM",
Edouard@2764: "VAR": "_ENUM"
Edouard@2765: }[node.vartype] + ", " +
Edouard@2768: str(buf_index) + ", 0, }"]
Edouard@2765: buf_index += sz
Edouard@2775: item_count += 1
Edouard@2764: if len(node.path) == 1:
Edouard@2764: extern_variables_declarations += [
Edouard@2764: "extern __IEC_" + node.iectype + "_" +
Edouard@2764: "t" if node.vartype is "VAR" else "p"
Edouard@2764: + ".".join(node.path) + ";"]
Edouard@2764:
Edouard@2764: # TODO : filter only requiered external declarations
Edouard@2764: for v in varlist :
Edouard@2764: if v["C_path"].find('.') < 0 and v["vartype"] == "FB" :
Edouard@2764: extern_variables_declarations += [
Edouard@2764: "extern %(type)s %(C_path)s;" % v]
Edouard@2764:
Edouard@2764: # TODO check if programs need to be declared separately
Edouard@2764: # "programs_declarations": "\n".join(["extern %(type)s %(C_path)s;" %
Edouard@2764: # p for p in self._ProgramList]),
Edouard@2764:
Edouard@2771: # C code to observe/access HMI tree variables
Edouard@2749: svghmi_c_filepath = paths.AbsNeighbourFile(__file__, "svghmi.c")
Edouard@2749: svghmi_c_file = open(svghmi_c_filepath, 'r')
Edouard@2749: svghmi_c_code = svghmi_c_file.read()
Edouard@2749: svghmi_c_file.close()
Edouard@2764: svghmi_c_code = svghmi_c_code % {
Edouard@2764: "variable_decl_array": ",\n".join(variable_decl_array),
Edouard@2764: "extern_variables_declarations": "\n".join(extern_variables_declarations),
Edouard@2767: "buffer_size": buf_index,
Edouard@2775: "item_count": item_count,
Edouard@2768: "var_access_code": targets.GetCode("var_access.c"),
Edouard@2788: "PLC_ticktime": self.GetCTR().GetTicktime(),
Edouard@2789: "hmi_hash_ints": ",".join(map(str,hmi_tree_root.hash()))
Edouard@2764: }
Edouard@2749:
Edouard@2749: gen_svghmi_c_path = os.path.join(buildpath, "svghmi.c")
Edouard@2749: gen_svghmi_c = open(gen_svghmi_c_path, 'w')
Edouard@2749: gen_svghmi_c.write(svghmi_c_code)
Edouard@2749: gen_svghmi_c.close()
Edouard@2749:
Edouard@2771: # Python based WebSocket HMITree Server
Edouard@2771: svghmiserverfile = open(paths.AbsNeighbourFile(__file__, "svghmi_server.py"), 'r')
Edouard@2771: svghmiservercode = svghmiserverfile.read()
Edouard@2771: svghmiserverfile.close()
Edouard@2771:
Edouard@2771: runtimefile_path = os.path.join(buildpath, "runtime_svghmi.py")
Edouard@2771: runtimefile = open(runtimefile_path, 'w')
Edouard@2771: runtimefile.write(svghmiservercode)
Edouard@2771: runtimefile.close()
Edouard@2771:
Edouard@2771: return ((["svghmi"], [(gen_svghmi_c_path, IECCFLAGS)], True), "",
Edouard@2771: ("runtime_svghmi0.py", open(runtimefile_path, "rb")))
Edouard@2745:
Edouard@2745: class SVGHMI(object):
Edouard@2771: XSD = """
Edouard@2745: