Merge default in SVGHMI branch svghmi
authorEdouard Tisserant
Tue, 24 Sep 2019 11:58:31 +0200
branchsvghmi
changeset 2784 35eeb1ed105f
parent 2783 5ee6967f721d (diff)
parent 2626 39d78c530cbb (current diff)
child 2785 29196dcb25b1
Merge default in SVGHMI branch
--- a/PLCGenerator.py	Tue Sep 24 11:55:59 2019 +0200
+++ b/PLCGenerator.py	Tue Sep 24 11:58:31 2019 +0200
@@ -935,7 +935,7 @@
             if invar.getformalParameter() == "EN":
                 if len(invar.getconnectionPointIn().getconnections()) > 0:
                     if blk.getinstanceName() is None:
-                        var_name = "%s%d_ENO" % (blk.gettypeName(), blk.getlocalId())
+                        var_name = "_TMP_%s%d_ENO" % (blk.gettypeName(), blk.getlocalId())
                     else:
                         var_name = "%s.ENO" % blk.getinstanceName()
                     return var_name
@@ -1160,7 +1160,7 @@
                             if variable.getformalParameter() == "":
                                 variable_name = "%s%d" % (type, block.getlocalId())
                             else:
-                                variable_name = "%s%d_%s" % (type, block.getlocalId(), parameter)
+                                variable_name = "_TMP_%s%d_%s" % (type, block.getlocalId(), parameter)
                             if self.Interface[-1][0] != "VAR" or self.Interface[-1][1] is not None or self.Interface[-1][2]:
                                 self.Interface.append(("VAR", None, False, []))
                             if variable.connectionPointOut in self.ConnectionTypes:
@@ -1253,7 +1253,7 @@
                     if output_parameter == "":
                         output_name = "%s%d" % (type, block.getlocalId())
                     else:
-                        output_name = "%s%d_%s" % (type, block.getlocalId(), output_parameter)
+                        output_name = "_TMP_%s%d_%s" % (type, block.getlocalId(), output_parameter)
                     output_value = [(output_name, output_info)]
                 return self.ExtractModifier(output_variable, output_value, output_info)
             if block_infos["type"] == "functionBlock":
--- a/ProjectController.py	Tue Sep 24 11:55:59 2019 +0200
+++ b/ProjectController.py	Tue Sep 24 11:58:31 2019 +0200
@@ -1053,7 +1053,8 @@
                     "FB":  "extern       %(type)s   %(C_path)s;"
                 }[v["vartype"]] % v
                 for v in self._VariablesList if v["C_path"].find('.') < 0]),
-            "variable_decl_array": ",\n".join(variable_decl_array)
+            "variable_decl_array": ",\n".join(variable_decl_array),
+            "var_access_code": targets.GetCode("var_access.c")
         }
 
         return debug_code
@@ -1163,6 +1164,19 @@
     def _Generate_runtime(self):
         buildpath = self._getBuildPath()
 
+        # CTN code gen is expected AFTER Libraries code gen,
+        # at least SVGHMI relies on it.
+
+        # Generate C code and compilation params from liraries
+        try:
+            LibCFilesAndCFLAGS, LibLDFLAGS, LibExtraFiles = self.GetLibrariesCCode(
+                buildpath)
+        except Exception:
+            self.logger.write_error(
+                _("Runtime library extensions C code generation failed !\n"))
+            self.logger.write_error(traceback.format_exc())
+            return False
+
         # Generate C code and compilation params from confnode hierarchy
         try:
             CTNLocationCFilesAndCFLAGS, CTNLDFLAGS, CTNExtraFiles = self._Generate_C(
@@ -1174,16 +1188,6 @@
             self.logger.write_error(traceback.format_exc())
             return False
 
-        # Generate C code and compilation params from liraries
-        try:
-            LibCFilesAndCFLAGS, LibLDFLAGS, LibExtraFiles = self.GetLibrariesCCode(
-                buildpath)
-        except Exception:
-            self.logger.write_error(
-                _("Runtime library extensions C code generation failed !\n"))
-            self.logger.write_error(traceback.format_exc())
-            return False
-
         self.LocationCFilesAndCFLAGS = LibCFilesAndCFLAGS + \
             CTNLocationCFilesAndCFLAGS
         self.LDFLAGS = CTNLDFLAGS + LibLDFLAGS
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/XSLTransform.py	Tue Sep 24 11:58:31 2019 +0200
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# This file is part of Beremiz.
+# See COPYING file for copyrights details.
+
+from __future__ import absolute_import
+from lxml import etree
+
+class XSLTransform(object):
+    """ a class to handle XSLT queries on project and libs """
+    def __init__(self, xsltpath, xsltext):
+
+        # parse and compile. "beremiz" arbitrary namespace for extensions
+        self.xslt = etree.XSLT(
+            etree.parse(
+                xsltpath,
+                etree.XMLParser()),
+            extensions={("beremiz", name): call for name, call in xsltext})
+
+    def transform(self, root, **kwargs):
+        res = self.xslt(root, **{k: etree.XSLT.strparam(v) for k, v in kwargs.iteritems()})
+        # print(self.xslt.error_log)
+        return res
+
+
--- a/docutil/docsvg.py	Tue Sep 24 11:55:59 2019 +0200
+++ b/docutil/docsvg.py	Tue Sep 24 11:58:31 2019 +0200
@@ -31,15 +31,19 @@
 
 def get_inkscape_path():
     """ Return the Inkscape path """
-    from six.moves import winreg
-    try:
-        svgexepath = winreg.QueryValue(winreg.HKEY_LOCAL_MACHINE,
-                                       'Software\\Classes\\svgfile\\shell\\Inkscape\\command')
-    except OSError:
-        svgexepath = winreg.QueryValue(winreg.HKEY_LOCAL_MACHINE,
-                                       'Software\\Classes\\inkscape.svg\\shell\\open\\command')
-    svgexepath = svgexepath.replace('"%1"', '')
-    return svgexepath.replace('"', '')
+    if wx.Platform == '__WXMSW__':
+        from six.moves import winreg
+        try:
+            svgexepath = winreg.QueryValue(winreg.HKEY_LOCAL_MACHINE,
+                                           'Software\\Classes\\svgfile\\shell\\Inkscape\\command')
+        except OSError:
+            svgexepath = winreg.QueryValue(winreg.HKEY_LOCAL_MACHINE,
+                                           'Software\\Classes\\inkscape.svg\\shell\\open\\command')
+        svgexepath = svgexepath.replace('"%1"', '').strip()
+        return svgexepath.replace('"', '')
+    else:
+        # TODO: search path
+        return os.path.join("/usr/bin", "inkscape")
 
 
 def open_win_svg(svgexepath, svgfile):
@@ -65,7 +69,7 @@
             wx.MessageBox("Inkscape is not found or installed !")
             return None
     else:
-        svgexepath = os.path.join("/usr/bin", "inkscape")
+        svgexepath=get_inkscape_path()
         if os.path.isfile(svgexepath):
             open_lin_svg(svgexepath, svgfile)
         else:
--- a/features.py	Tue Sep 24 11:55:59 2019 +0200
+++ b/features.py	Tue Sep 24 11:58:31 2019 +0200
@@ -12,7 +12,8 @@
     ('Native', 'NativeLib.NativeLibrary', True),
     ('Python', 'py_ext.PythonLibrary', True),
     ('Etherlab', 'etherlab.EthercatMaster.EtherlabLibrary', False),
-    ('SVGUI', 'svgui.SVGUILibrary', False)]
+    ('SVGUI', 'svgui.SVGUILibrary', False),
+    ('SVGHMI', 'svghmi.SVGHMILibrary', False)]
 
 catalog = [
     ('canfestival', _('CANopen support'), _('Map located variables over CANopen'), 'canfestival.canfestival.RootClass'),
@@ -22,6 +23,7 @@
     ('c_ext', _('C extension'), _('Add C code accessing located variables synchronously'), 'c_ext.CFile'),
     ('py_ext', _('Python file'), _('Add Python code executed asynchronously'), 'py_ext.PythonFile'),
     ('wxglade_hmi', _('WxGlade GUI'), _('Add a simple WxGlade based GUI.'), 'wxglade_hmi.WxGladeHMI'),
-    ('svgui', _('SVGUI'), _('Experimental web based HMI'), 'svgui.SVGUI')]
+    ('svgui', _('SVGUI'), _('Experimental web based HMI'), 'svgui.SVGUI'),
+    ('svghmi', _('SVGHMI'), _('SVG based HMI'), 'svghmi.SVGHMI')]
 
 file_editors = []
--- a/plcopen/Makefile	Tue Sep 24 11:55:59 2019 +0200
+++ b/plcopen/Makefile	Tue Sep 24 11:58:31 2019 +0200
@@ -1,13 +1,23 @@
 #! gmake
 
-yml := ../../yml2
+# Makefile to generate XSLT stylesheets from ysl2 files in the same directory
+
+# This uses YML2. 
+#   hg clone https://pep.foundation/dev/repos/yml2/
+
+# It should be just fine if yml2 is cloned just asside beremiz
+# otherwise, point yml2path to yml2 source directory
+#   make yml2path=path/to/yml/dir
+
+yml2path ?= $(abspath ../../yml2)
+
 ysl2files := $(wildcard *.ysl2)
 xsltfiles := $(patsubst %.ysl2, %.xslt, $(ysl2files))
 
 all:$(xsltfiles)
 
-%.xslt: %.ysl2 yslt_noindent.yml2
-	$(yml)/yml2c -I $(yml) $< -o $@.tmp
+%.xslt: %.ysl2 ../yslt_noindent.yml2
+	$(yml2path)/yml2c -I $(yml2path):../ $< -o $@.tmp
 	xmlstarlet fo $@.tmp > $@
 	rm $@.tmp
 	
--- a/plcopen/XSLTModelQuery.py	Tue Sep 24 11:55:59 2019 +0200
+++ b/plcopen/XSLTModelQuery.py	Tue Sep 24 11:58:31 2019 +0200
@@ -8,13 +8,14 @@
 from lxml import etree
 import util.paths as paths
 from plcopen.structures import StdBlckLibs
+from XSLTransform import XSLTransform
 
 ScriptDirectory = paths.AbsDir(__file__)
 
-
-class XSLTModelQuery(object):
+class XSLTModelQuery(XSLTransform):
     """ a class to handle XSLT queries on project and libs """
     def __init__(self, controller, xsltpath, ext=None):
+
         # arbitrary set debug to false, updated later
         self.debug = False
 
@@ -31,19 +32,12 @@
         if ext is not None:
             xsltext.extend(ext)
 
-        # parse and compile. "beremiz" arbitrary namespace for extensions
-        self.xslt = etree.XSLT(
-            etree.parse(
-                os.path.join(ScriptDirectory, xsltpath),
-                etree.XMLParser()),
-            extensions={("beremiz", name): call for name, call in xsltext})
-
+        XSLTransform.__init__(self,
+                           os.path.join(ScriptDirectory, xsltpath),
+                           xsltext)
     def _process_xslt(self, root, debug, **kwargs):
         self.debug = debug
-        res = self.xslt(root, **{k: etree.XSLT.strparam(v) for k, v in kwargs.iteritems()})
-        # print(self.xslt.error_log)
-        return res
-
+        return self.transform(root, **kwargs)
 
 # -------------------------------------------------------------------------------
 #           Helpers functions for translating list of arguments
--- a/plcopen/yslt_noindent.yml2	Tue Sep 24 11:55:59 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,32 +0,0 @@
-include yslt.yml2
-
-in xsl {
-    decl istylesheet (
-        *output="xml",
-        version="1.0",
-        xmlns:xsl="http://www.w3.org/1999/XSL/Transform",
-        xmlns:exsl='http://exslt.org/common',
-        extension-element-prefixes='exsl'
-    ) alias stylesheet {
-        output *output;
-        content;
-    };
-
-    decl template(match) {
-        content;
-    };
-
-    decl function(name) alias template {
-        content;
-    };
-   
-    decl call(name) alias call-template {
-        content;
-    };
-
-    decl apply(select) alias apply-templates {
-        content;
-    };
-}
-
-
--- a/runtime/PLCObject.py	Tue Sep 24 11:55:59 2019 +0200
+++ b/runtime/PLCObject.py	Tue Sep 24 11:58:31 2019 +0200
@@ -319,12 +319,15 @@
 
         return False
 
-    def PythonRuntimeCall(self, methodname, use_evaluator=True):
+    def PythonRuntimeCall(self, methodname, use_evaluator=True, reverse_order=False):
         """
         Calls init, start, stop or cleanup method provided by
         runtime python files, loaded when new PLC uploaded
         """
-        for method in self.python_runtime_vars.get("_runtime_%s" % methodname, []):
+        methods = self.python_runtime_vars.get("_runtime_%s" % methodname, [])
+        if reverse_order:
+            methods = reversed(methods)
+        for method in methods:
             if use_evaluator:
                 _res, exp = self.evaluator(method)
             else:
@@ -395,7 +398,7 @@
         if self.python_runtime_vars is not None:
             self.PythonThreadCommand("Finish")
             self.PythonThread.join()
-            self.PythonRuntimeCall("cleanup", use_evaluator=False)
+            self.PythonRuntimeCall("cleanup", use_evaluator=False, reverse_order=True)
 
         self.python_runtime_vars = None
 
@@ -438,7 +441,7 @@
             if cmd == "Activate":
                 self.PythonRuntimeCall("start")
                 self.PythonThreadLoop()
-                self.PythonRuntimeCall("stop")
+                self.PythonRuntimeCall("stop", reverse_order=True)
             else:  # "Finish"
                 break
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/svghmi/Makefile	Tue Sep 24 11:58:31 2019 +0200
@@ -0,0 +1,25 @@
+#! gmake
+
+# Makefile to generate XSLT stylesheets from ysl2 files in the same directory
+
+# This uses YML2. 
+#   hg clone https://pep.foundation/dev/repos/yml2/
+
+# It should be just fine if yml2 is cloned just asside beremiz
+# otherwise, point yml2path to yml2 source directory
+#   make yml2path=path/to/yml/dir
+
+yml2path ?= $(abspath ../../yml2)
+
+ysl2files := $(wildcard *.ysl2)
+xsltfiles := $(patsubst %.ysl2, %.xslt, $(ysl2files))
+
+all:$(xsltfiles)
+
+%.xslt: %.ysl2 ../yslt_noindent.yml2
+	$(yml2path)/yml2c -I $(yml2path):../ $< -o $@.tmp
+	xmlstarlet fo $@.tmp > $@
+	rm $@.tmp
+	
+clean:
+	rm -f $(xsltfiles)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/svghmi/README	Tue Sep 24 11:58:31 2019 +0200
@@ -0,0 +1,1 @@
+SVG HMI
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/svghmi/__init__.py	Tue Sep 24 11:58:31 2019 +0200
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# This file is part of Beremiz
+# Copyright (C) 2019: Edouard TISSERANT
+#
+# See COPYING file for copyrights details.
+
+from __future__ import absolute_import
+from svghmi.svghmi import *
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/svghmi/default.svg	Tue Sep 24 11:58:31 2019 +0200
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="1280"
+   height="720"
+   viewBox="0 0 1280 720"
+   version="1.1"
+   id="hmi0"
+   sodipodi:docname="default.svg"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)">
+  <metadata
+     id="metadata4542">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs2" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:document-units="px"
+     inkscape:current-layer="hmi0"
+     showgrid="false"
+     units="px"
+     inkscape:zoom="0.7"
+     inkscape:cx="576.80864"
+     inkscape:cy="330.28432"
+     inkscape:window-width="1600"
+     inkscape:window-height="886"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1" />
+  <rect
+     style="color:#000000"
+     id="page0"
+     width="1280"
+     height="720"
+     x="0"
+     y="0">
+    <desc
+       id="desc_page0">This is description for page 0
+
+all lines in the form &quot;name: value&quot; 
+are used as js object definition initializer
+
+role: &quot;page&quot;
+name: &quot;Home&quot;
+
+after triple opening braces is global JavaScript code
+
+{{{
+/* JS style Comment */
+alert(&quot;Hello World&quot;);
+}}}
+
+after triple closing braces is back to description
+</desc>
+  </rect>
+  <text
+     xml:space="preserve"
+     style="font-style:normal;font-weight:normal;font-size:160px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     x="436.32812"
+     y="418.24219"
+     id="text5151"
+     inkscape:label="count"><desc
+       id="desc5153">path: &quot;count&quot;
+format: &quot;%4.4d&quot;</desc><tspan
+       sodipodi:role="line"
+       id="tspan5149"
+       x="436.32812"
+       y="418.24219"
+       style="stroke-width:1px">8888</tspan></text>
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/svghmi/gen_index_xhtml.xslt	Tue Sep 24 11:58:31 2019 +0200
@@ -0,0 +1,145 @@
+<?xml version="1.0"?>
+<xsl:stylesheet xmlns:svg="http://www.w3.org/2000/svg" xmlns:ns="beremiz" xmlns:cc="http://creativecommons.org/ns#" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/1999/xhtml" xmlns:str="http://exslt.org/strings" xmlns:regexp="http://exslt.org/regular-expressions" xmlns:exsl="http://exslt.org/common" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" exclude-result-prefixes="ns" extension-element-prefixes="ns" version="1.0">
+  <xsl:output method="xml" cdata-section-elements="script"/>
+  <xsl:variable name="geometry" select="ns:GetSVGGeometry()"/>
+  <xsl:variable name="hmitree" select="ns:GetHMITree()"/>
+  <xsl:template match="@* | node()">
+    <xsl:copy>
+      <xsl:apply-templates select="@* | node()"/>
+    </xsl:copy>
+  </xsl:template>
+  <xsl:variable name="mark">
+    <xsl:text>=HMI=
+</xsl:text>
+  </xsl:variable>
+  <xsl:template match="/">
+    <html xmlns="http://www.w3.org/1999/xhtml">
+      <head/>
+      <body style="margin:0;">
+        <xsl:copy>
+          <xsl:comment>
+            <xsl:apply-templates mode="testgeo" select="$geometry"/>
+          </xsl:comment>
+          <xsl:comment>
+            <xsl:apply-templates mode="testtree" select="$hmitree"/>
+          </xsl:comment>
+          <xsl:apply-templates select="@* | node()"/>
+        </xsl:copy>
+        <script>
+          <xsl:text>function evaluate_js_from_descriptions() {
+</xsl:text>
+          <xsl:text>    var Page;
+</xsl:text>
+          <xsl:text>    var Input;
+</xsl:text>
+          <xsl:text>    var Display;
+</xsl:text>
+          <xsl:text>    var res = [];
+</xsl:text>
+          <xsl:variable name="midmark">
+            <xsl:text>
+</xsl:text>
+            <xsl:value-of select="$mark"/>
+          </xsl:variable>
+          <xsl:apply-templates mode="code_from_descs" select="//*[contains(child::svg:desc, $midmark) or                                starts-with(child::svg:desc, $mark)]"/>
+          <xsl:text>    return res;
+</xsl:text>
+          <xsl:text>}
+</xsl:text>
+          <xsl:text>(function(){
+</xsl:text>
+          <xsl:text>    var relative_URI = window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws');
+</xsl:text>
+          <xsl:text>    var ws = new WebSocket(relative_URI);
+</xsl:text>
+          <xsl:text>    ws.onmessage = function (evt) {
+</xsl:text>
+          <xsl:text>        var received_msg = evt.data;
+</xsl:text>
+          <xsl:text>        alert("Message is received..."+received_msg); 
+</xsl:text>
+          <xsl:text>    };
+</xsl:text>
+          <xsl:text>    ws.onopen = function (evt) {
+</xsl:text>
+          <xsl:text>        ws.send("test");
+</xsl:text>
+          <xsl:text>    };
+</xsl:text>
+          <xsl:text>})();
+</xsl:text>
+        </script>
+      </body>
+    </html>
+  </xsl:template>
+  <xsl:template mode="code_from_descs" match="*">
+    <xsl:text>{
+</xsl:text>
+    <xsl:text>    var path, role, name, priv;
+</xsl:text>
+    <xsl:text>    var id = "</xsl:text>
+    <xsl:value-of select="@id"/>
+    <xsl:text>";
+</xsl:text>
+    <xsl:if test="&#xAB;@inkscape:label&#xBB;">
+      <xsl:text>name = "</xsl:text>
+      <xsl:value-of select="@inkscape:label"/>
+      <xsl:text>";
+</xsl:text>
+    </xsl:if>
+    <xsl:text>/* -------------- */
+</xsl:text>
+    <xsl:value-of select="substring-after(svg:desc, $mark)"/>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>    /* -------------- */
+</xsl:text>
+    <xsl:text>    res.push({
+</xsl:text>
+    <xsl:text>        path:path,
+</xsl:text>
+    <xsl:text>        role:role,
+</xsl:text>
+    <xsl:text>        name:name,
+</xsl:text>
+    <xsl:text>        priv:priv
+</xsl:text>
+    <xsl:text>    })
+</xsl:text>
+    <xsl:text>}
+</xsl:text>
+  </xsl:template>
+  <xsl:template mode="testgeo" match="bbox">
+    <xsl:text>ID: </xsl:text>
+    <xsl:value-of select="@Id"/>
+    <xsl:text> x: </xsl:text>
+    <xsl:value-of select="@x"/>
+    <xsl:text> y: </xsl:text>
+    <xsl:value-of select="@y"/>
+    <xsl:text> w: </xsl:text>
+    <xsl:value-of select="@w"/>
+    <xsl:text> h: </xsl:text>
+    <xsl:value-of select="@h"/>
+    <xsl:text>
+</xsl:text>
+  </xsl:template>
+  <xsl:template mode="testtree" match="*">
+    <xsl:param name="indent" select="''"/>
+    <xsl:value-of select="$indent"/>
+    <xsl:text> </xsl:text>
+    <xsl:value-of select="local-name()"/>
+    <xsl:text> </xsl:text>
+    <xsl:value-of select="@name"/>
+    <xsl:text> </xsl:text>
+    <xsl:value-of select="@type"/>
+    <xsl:text> </xsl:text>
+    <xsl:value-of select="@path"/>
+    <xsl:text>
+</xsl:text>
+    <xsl:apply-templates mode="testtree" select="*">
+      <xsl:with-param name="indent">
+        <xsl:value-of select="concat($indent,'&gt;')"/>
+      </xsl:with-param>
+    </xsl:apply-templates>
+  </xsl:template>
+</xsl:stylesheet>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/svghmi/gen_index_xhtml.ysl2	Tue Sep 24 11:58:31 2019 +0200
@@ -0,0 +1,122 @@
+include yslt_noindent.yml2
+
+// overrides yslt's output function to set CDATA
+decl output(method, cdata-section-elements="script");
+istylesheet 
+            /* From Inkscape */
+            xmlns:dc="http://purl.org/dc/elements/1.1/"
+            xmlns:cc="http://creativecommons.org/ns#"
+            xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+            xmlns:svg="http://www.w3.org/2000/svg"
+            xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+            xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+            xmlns="http://www.w3.org/1999/xhtml"
+
+            /* Our namespace to invoke python code */
+            xmlns:ns="beremiz"
+            extension-element-prefixes="ns" 
+            exclude-result-prefixes="ns" {
+    
+    /* This retrieves geometry obtained through "inkscape -S" 
+     * already parsed by python and presented as a list of 
+     * <bbox x="0" y="0" w="42" h="42">
+     */
+    variable "geometry", "ns:GetSVGGeometry()";
+    variable "hmitree", "ns:GetHMITree()";
+   
+    /* Identity template :
+     *  - copy every attributes 
+     *  - copy every sub-elements
+     */
+    template "@* | node()" {
+      /* use real xsl:copy instead copy-of alias from yslt.yml2 */
+      xsl:copy apply "@* | node()";
+    }
+
+    variable "mark" > =HMI=\n
+
+    /* copy root node and add geometry as comment for a test */
+    template "/" 
+      html xmlns="http://www.w3.org/1999/xhtml" {
+          head;
+          body style="margin:0;" {
+              xsl:copy {
+                  comment {
+                      apply "$geometry", mode="testgeo";
+                  }
+                  comment {
+                      apply "$hmitree", mode="testtree";
+                  }
+                  apply "@* | node()";
+              }
+              script{
+                  ||
+                  function evaluate_js_from_descriptions() {
+                      var Page;
+                      var Input;
+                      var Display;
+                      var res = [];
+                  ||
+                  variable "midmark" > \n«$mark»
+                  apply """//*[contains(child::svg:desc, $midmark) or \
+                               starts-with(child::svg:desc, $mark)]""",2 
+                        mode="code_from_descs";
+                  ||
+                      return res;
+                  }
+                  ||
+
+                  /*TODO add :
+                    - pages content
+                      + with ref to elt ?
+                    - widgets parameters
+                  */
+
+                  include text svghmi.js
+              }
+          }
+    }
+
+    template "*", mode="code_from_descs" {
+        ||
+        {
+            var path, role, name, priv;
+            var id = "«@id»";
+        ||
+
+        /* if label is used, use it as default name */
+        if "«@inkscape:label»"
+            |> name = "«@inkscape:label»";
+
+        | /* -------------- */
+
+        // this breaks indent, but fixing indent could break string literals
+        value "substring-after(svg:desc, $mark)";
+        // nobody reads generated code anyhow...
+
+        ||
+
+            /* -------------- */
+            res.push({
+                path:path,
+                role:role,
+                name:name,
+                priv:priv
+            })
+        }
+        ||
+    }
+
+
+    template "bbox", mode="testgeo"{
+        | ID: «@Id» x: «@x» y: «@y» w: «@w» h: «@h»
+    }
+
+    template "*", mode="testtree"{
+        param "indent", "''";
+        | «$indent» «local-name()» «@name» «@type» «@path»
+        apply "*", mode="testtree" {
+            with "indent" value "concat($indent,'>')"
+        };
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/svghmi/pous.xml	Tue Sep 24 11:58:31 2019 +0200
@@ -0,0 +1,55 @@
+<?xml version='1.0' encoding='utf-8'?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.plcopen.org/xml/tc6_0201" xmlns:xhtml="http://www.w3.org/1999/xhtml" xsi:schemaLocation="http://www.plcopen.org/xml/tc6_0201">
+  <fileHeader companyName="Beremiz" productName="Beremiz" productVersion="0.0" creationDateTime="2008-12-14T16:53:26"/>
+  <contentHeader name="Beremiz non-standard POUs library" modificationDateTime="2019-08-06T14:08:26">
+    <coordinateInfo>
+      <fbd>
+        <scaling x="0" y="0"/>
+      </fbd>
+      <ld>
+        <scaling x="0" y="0"/>
+      </ld>
+      <sfc>
+        <scaling x="0" y="0"/>
+      </sfc>
+    </coordinateInfo>
+  </contentHeader>
+  <types>
+    <dataTypes>
+      <dataType name="HMI_INT">
+        <baseType>
+          <INT/>
+        </baseType>
+      </dataType>
+      <dataType name="HMI_REAL">
+        <baseType>
+          <REAL/>
+        </baseType>
+      </dataType>
+      <dataType name="HMI_STRING">
+        <baseType>
+          <string/>
+        </baseType>
+      </dataType>
+      <dataType name="HMI_BOOL">
+        <baseType>
+          <BOOL/>
+        </baseType>
+      </dataType>
+      <dataType name="HMI_CLASS">
+        <baseType>
+          <BOOL/>
+        </baseType>
+      </dataType>
+      <dataType name="HMI_LABEL">
+        <baseType>
+          <BOOL/>
+        </baseType>
+      </dataType>
+    </dataTypes>
+    <pous/>
+  </types>
+  <instances>
+    <configurations/>
+  </instances>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/svghmi/svghmi.c	Tue Sep 24 11:58:31 2019 +0200
@@ -0,0 +1,213 @@
+#include <pthread.h>
+#include <errno.h>
+#include "iec_types_all.h"
+#include "POUS.h"
+#include "config.h"
+#include "beremiz.h"
+
+#define DEFAULT_REFRESH_PERIOD_MS 100
+#define HMI_BUFFER_SIZE %(buffer_size)d
+#define HMI_ITEM_COUNT %(item_count)d
+
+/* PLC reads from that buffer */
+static char rbuf[HMI_BUFFER_SIZE];
+
+/* PLC writes to that buffer */
+static char wbuf[HMI_BUFFER_SIZE];
+
+%(extern_variables_declarations)s
+
+#define ticktime_ns %(PLC_ticktime)d
+uint16_t ticktime_ms = (ticktime_ns>1000000)?
+                     ticktime_ns/1000000:
+                     1;
+
+typedef enum {
+    buf_free = 0,
+    buf_set,
+    buf_tosend
+} buf_state_t;
+
+int global_write_dirty = 0;
+
+typedef struct {
+    void *ptr;
+    __IEC_types_enum type;
+    uint32_t buf_index;
+
+    /* publish/write/send */
+    long wlock;
+    /* zero means not subscribed */
+    uint16_t refresh_period_ms;
+    uint16_t age_ms;
+
+    buf_state_t wstate;
+
+    /* retrieve/read/recv */
+    long rlock;
+    buf_state_t rstate;
+
+} hmi_tree_item_t;
+
+static hmi_tree_item_t hmi_tree_item[] = {
+%(variable_decl_array)s
+};
+
+static char sendbuf[HMI_BUFFER_SIZE];
+
+typedef void(*hmi_tree_iterator)(hmi_tree_item_t*);
+void traverse_hmi_tree(hmi_tree_iterator fp)
+{
+    unsigned int i;
+    for(i = 0; i < sizeof(hmi_tree_item)/sizeof(hmi_tree_item_t); i++){
+        hmi_tree_item_t *dsc = &hmi_tree_item[i];
+        if(dsc->type != UNKNOWN_ENUM) 
+            (*fp)(dsc);
+    }
+}
+
+#define __Unpack_desc_type hmi_tree_item_t
+
+%(var_access_code)s
+
+void write_iterator(hmi_tree_item_t *dsc)
+{
+    void *dest_p = &wbuf[dsc->buf_index];
+    void *real_value_p = NULL;
+    char flags = 0;
+
+    void *visible_value_p = UnpackVar(dsc, &real_value_p, &flags);
+
+    /* Try take lock */
+    long was_locked = AtomicCompareExchange(&dsc->wlock, 0, 1);
+
+    if(was_locked) {
+        /* was locked. give up*/
+        return;
+    }
+
+    if(dsc->wstate == buf_set){
+        /* if being subscribed */
+        if(dsc->refresh_period_ms){
+            if(dsc->age_ms + ticktime_ms < dsc->refresh_period_ms){
+                dsc->age_ms += ticktime_ms;
+            }else{
+                dsc->wstate = buf_tosend;
+            }
+        }
+    }
+
+    /* if new value differs from previous one */
+    if(memcmp(dest_p, visible_value_p, __get_type_enum_size(dsc->type)) != 0){
+        /* copy and flag as set */
+        memcpy(dest_p, visible_value_p, __get_type_enum_size(dsc->type));
+        if(dsc->wstate == buf_free) {
+            dsc->wstate = buf_set;
+            dsc->age_ms = 0;
+        }
+        global_write_dirty = 1;
+    }
+
+    /* unlock - use AtomicComparExchange to have memory barrier */
+    AtomicCompareExchange(&dsc->wlock, 1, 0);
+}
+
+struct timespec sending_now;
+struct timespec next_sending;
+void send_iterator(hmi_tree_item_t *dsc)
+{
+    while(AtomicCompareExchange(&dsc->wlock, 0, 1)) sched_yield();
+
+    // check for variable being modified
+    if(dsc->wstate == buf_tosend){
+        // send 
+
+        // TODO pack data in buffer
+
+        dsc->wstate = buf_free;
+    }
+
+    AtomicCompareExchange(&dsc->wlock, 1, 0);
+}
+
+void read_iterator(hmi_tree_item_t *dsc)
+{
+    void *src_p = &rbuf[dsc->buf_index];
+    void *real_value_p = NULL;
+    char flags = 0;
+
+    void *visible_value_p = UnpackVar(dsc, &real_value_p, &flags);
+
+
+    memcpy(visible_value_p, src_p, __get_type_enum_size(dsc->type));
+}
+
+static pthread_cond_t svghmi_send_WakeCond = PTHREAD_COND_INITIALIZER;
+static pthread_mutex_t svghmi_send_WakeCondLock = PTHREAD_MUTEX_INITIALIZER;
+
+static int continue_collect;
+
+int __init_svghmi()
+{
+    bzero(rbuf,sizeof(rbuf));
+    bzero(wbuf,sizeof(wbuf));
+    continue_collect = 1;
+
+    return 0;
+}
+
+void __cleanup_svghmi()
+{
+    pthread_mutex_lock(&svghmi_send_WakeCondLock);
+    continue_collect = 0;
+    pthread_cond_signal(&svghmi_send_WakeCond);
+    pthread_mutex_unlock(&svghmi_send_WakeCondLock);
+}
+
+void __retrieve_svghmi()
+{
+    traverse_hmi_tree(read_iterator);
+}
+
+void __publish_svghmi()
+{
+    global_write_dirty = 0;
+    traverse_hmi_tree(write_iterator);
+    if(global_write_dirty) {
+        pthread_cond_signal(&svghmi_send_WakeCond);
+    }
+}
+
+/* PYTHON CALLS */
+int svghmi_send_collect(uint32_t *size, char **ptr){
+
+    int do_collect;
+    pthread_mutex_lock(&svghmi_send_WakeCondLock);
+    do_collect = continue_collect;
+    if(do_collect){
+        pthread_cond_wait(&svghmi_send_WakeCond, &svghmi_send_WakeCondLock);
+        do_collect = continue_collect;
+    }
+    pthread_mutex_unlock(&svghmi_send_WakeCondLock);
+
+
+    if(do_collect) {
+        traverse_hmi_tree(send_iterator);
+        /* TODO set ptr and size to something  */
+        return 0;
+    }
+    else
+    {
+        return EINTR;
+    }
+}
+
+int svghmi_recv_dispatch(uint32_t size, char *ptr){
+    printf("%%*s",size,ptr);
+    /* TODO something with ptr and size
+        - subscribe
+         or
+        - spread values
+    */
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/svghmi/svghmi.js	Tue Sep 24 11:58:31 2019 +0200
@@ -0,0 +1,22 @@
+// svghmi.js
+
+(function(){
+    // Open WebSocket to relative "/ws" address
+    var ws = new WebSocket(window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws'));
+
+    // Register message reception handler 
+    ws.onmessage = function (evt) {
+        // TODO : dispatch and cache hmi tree updates
+
+        var received_msg = evt.data;
+        alert("Message is received..."+received_msg); 
+    };
+
+    // Once connection established
+    ws.onopen = function (evt) {
+        // TODO : enable the HMI (was previously offline, or just starts)
+        //        show main page
+
+        ws.send("test");
+    };
+})();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/svghmi/svghmi.py	Tue Sep 24 11:58:31 2019 +0200
@@ -0,0 +1,401 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# This file is part of Beremiz
+# Copyright (C) 2019: Edouard TISSERANT
+#
+# See COPYING file for copyrights details.
+
+from __future__ import absolute_import
+import os
+import shutil
+from itertools import izip, imap
+from pprint import pprint, pformat
+
+import wx
+
+import util.paths as paths
+from POULibrary import POULibrary
+from docutil import open_svg, get_inkscape_path
+from lxml import etree
+
+from util.ProcessLogger import ProcessLogger
+from runtime.typemapping import DebugTypesSize
+import targets
+
+HMI_TYPES_DESC = {
+    "HMI_CLASS":{},
+    "HMI_LABEL":{},
+    "HMI_STRING":{},
+    "HMI_INT":{},
+    "HMI_REAL":{}
+}
+
+HMI_TYPES = HMI_TYPES_DESC.keys()
+
+from XSLTransform import XSLTransform
+
+ScriptDirectory = paths.AbsDir(__file__)
+
+class HMITreeNode(object):
+    def __init__(self, path, name, nodetype, iectype = None, vartype = None):
+        self.path = path
+        self.name = name
+        self.nodetype = nodetype
+
+        if iectype is not None:
+            self.iectype = iectype
+            self.vartype = vartype
+        if nodetype in ["HMI_LABEL", "HMI_ROOT"]:
+            self.children = []
+
+    def pprint(self, indent = 0):
+        res = ">"*indent + pformat(self.__dict__, indent = indent, depth = 1) + "\n"
+        if hasattr(self, "children"): 
+            res += "\n".join([child.pprint(indent = indent + 1)
+                              for child in self.children])
+            res += "\n"
+            
+        return res
+
+    def place_node(self, node):
+        best_child = None
+        known_best_match = 0
+        for child in self.children : 
+            if child.path is not None:
+                in_common = 0
+                for child_path_item, node_path_item in izip(child.path, node.path):
+                    if child_path_item == node_path_item:
+                        in_common +=1
+                    else:
+                        break
+                if in_common > known_best_match:
+                    known_best_match = in_common
+                    best_child = child
+        if best_child is not None and best_child.nodetype == "HMI_LABEL":
+            best_child.place_node(node)
+        else:
+            self.children.append(node)
+            
+    def etree(self):
+
+        attribs = dict(name=self.name)
+        if self.path is not None:
+            attribs["path"]=".".join(self.path)
+
+        res = etree.Element(self.nodetype, **attribs)
+
+        if hasattr(self, "children"): 
+            for child_etree in imap(lambda c:c.etree(), self.children):
+                res.append(child_etree)
+
+        return res
+
+    def traverse(self):
+        yield self
+        if hasattr(self, "children"): 
+            for c in self.children:
+                for yoodl in c.traverse():
+                    yield yoodl
+
+# module scope for HMITree root
+# so that CTN can use HMITree deduced in Library
+# note: this only works because library's Generate_C is 
+#       systematicaly invoked before CTN's CTNGenerate_C
+
+hmi_tree_root = None
+
+class SVGHMILibrary(POULibrary):
+    def GetLibraryPath(self):
+         return paths.AbsNeighbourFile(__file__, "pous.xml")
+
+    def Generate_C(self, buildpath, varlist, IECCFLAGS):
+        global hmi_tree_root
+
+        """
+        PLC Instance Tree:
+          prog0
+           +->v1 HMI_INT
+           +->v2 HMI_INT
+           +->fb0 (type mhoo)
+           |   +->va HMI_LABEL
+           |   +->v3 HMI_INT
+           |   +->v4 HMI_INT
+           |
+           +->fb1 (type mhoo)
+           |   +->va HMI_LABEL
+           |   +->v3 HMI_INT
+           |   +->v4 HMI_INT
+           |
+           +->fb2
+               +->v5 HMI_IN
+
+        HMI tree:
+          hmi0
+           +->v1
+           +->v2
+           +->fb0_va
+           |   +-> v3
+           |   +-> v4
+           |
+           +->fb1_va
+           |   +-> v3
+           |   +-> v4
+           |
+           +->v5
+
+        """
+
+        # Filter known HMI types
+        hmi_types_instances = [v for v in varlist if v["derived"] in HMI_TYPES]
+
+        hmi_tree_root = HMITreeNode(None, "/", "HMI_ROOT")
+
+        # add special nodes 
+        map(lambda (n,t): hmi_tree_root.children.append(HMITreeNode(None,n,t)), [
+                ("plc_status", "HMI_PLC_STATUS"),
+                ("current_page", "HMI_CURRENT_PAGE")])
+
+        # deduce HMI tree from PLC HMI_* instances
+        for v in hmi_types_instances:
+            path = v["C_path"].split(".")
+            # ignores variables starting with _TMP_
+            if path[-1].startswith("_TMP_"):
+                continue
+            new_node = HMITreeNode(path, path[-1], v["derived"], v["type"], v["vartype"])
+            hmi_tree_root.place_node(new_node)
+
+        variable_decl_array = []
+        extern_variables_declarations = []
+        buf_index = 0
+        item_count = 0
+        for node in hmi_tree_root.traverse():
+            if hasattr(node, "iectype"):
+                sz = DebugTypesSize.get(node.iectype, 0)
+                variable_decl_array += [
+                    "{&(" + ".".join(node.path) + "), " + node.iectype + {
+                        "EXT": "_P_ENUM",
+                        "IN":  "_P_ENUM",
+                        "MEM": "_O_ENUM",
+                        "OUT": "_O_ENUM",
+                        "VAR": "_ENUM"
+                    }[node.vartype] + ", " +
+                    str(buf_index) + ", 0, }"]
+                buf_index += sz
+                item_count += 1
+                if len(node.path) == 1:
+                    extern_variables_declarations += [
+                        "extern __IEC_" + node.iectype + "_" +
+                        "t" if node.vartype is "VAR" else "p"
+                        + ".".join(node.path) + ";"]
+
+        # TODO : filter only requiered external declarations
+        for v in varlist :
+            if v["C_path"].find('.') < 0 and v["vartype"] == "FB" :
+                extern_variables_declarations += [
+                    "extern %(type)s %(C_path)s;" % v]
+
+        # TODO check if programs need to be declared separately
+        # "programs_declarations": "\n".join(["extern %(type)s %(C_path)s;" %
+        #                                     p for p in self._ProgramList]),
+
+        # C code to observe/access HMI tree variables
+        svghmi_c_filepath = paths.AbsNeighbourFile(__file__, "svghmi.c")
+        svghmi_c_file = open(svghmi_c_filepath, 'r')
+        svghmi_c_code = svghmi_c_file.read()
+        svghmi_c_file.close()
+        svghmi_c_code = svghmi_c_code % { 
+            "variable_decl_array": ",\n".join(variable_decl_array),
+            "extern_variables_declarations": "\n".join(extern_variables_declarations),
+            "buffer_size": buf_index,
+            "item_count": item_count,
+            "var_access_code": targets.GetCode("var_access.c"),
+            "PLC_ticktime": self.GetCTR().GetTicktime()
+            }
+
+        gen_svghmi_c_path = os.path.join(buildpath, "svghmi.c")
+        gen_svghmi_c = open(gen_svghmi_c_path, 'w')
+        gen_svghmi_c.write(svghmi_c_code)
+        gen_svghmi_c.close()
+
+        # Python based WebSocket HMITree Server
+        svghmiserverfile = open(paths.AbsNeighbourFile(__file__, "svghmi_server.py"), 'r')
+        svghmiservercode = svghmiserverfile.read()
+        svghmiserverfile.close()
+
+        runtimefile_path = os.path.join(buildpath, "runtime_svghmi.py")
+        runtimefile = open(runtimefile_path, 'w')
+        runtimefile.write(svghmiservercode)
+        runtimefile.close()
+
+        return ((["svghmi"], [(gen_svghmi_c_path, IECCFLAGS)], True), "",
+                ("runtime_svghmi0.py", open(runtimefile_path, "rb")))
+
+class SVGHMI(object):
+    XSD = """<?xml version="1.0" encoding="utf-8" ?>
+    <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+      <xsd:element name="SVGHMI">
+        <xsd:complexType>
+          <xsd:attribute name="enableHTTP" type="xsd:boolean" use="optional" default="false"/>
+          <xsd:attribute name="bindAddress" type="xsd:string" use="optional" default="localhost"/>
+          <xsd:attribute name="port" type="xsd:string" use="optional" default="8080"/>
+        </xsd:complexType>
+      </xsd:element>
+    </xsd:schema>
+    """
+
+    ConfNodeMethods = [
+        {
+            "bitmap":    "ImportSVG",
+            "name":    _("Import SVG"),
+            "tooltip": _("Import SVG"),
+            "method":   "_ImportSVG"
+        },
+        {
+            "bitmap":    "ImportSVG",  # should be something different
+            "name":    _("Inkscape"),
+            "tooltip": _("Edit HMI"),
+            "method":   "_StartInkscape"
+        },
+
+        # TODO : HMITree button
+        #        - can drag'n'drop variabes to Inkscape
+
+    ]
+
+    def _getSVGpath(self, project_path=None):
+        if project_path is None:
+            project_path = self.CTNPath()
+        return os.path.join(project_path, "svghmi.svg")
+
+
+    def OnCTNSave(self, from_project_path=None):
+        if from_project_path is not None:
+            shutil.copyfile(self._getSVGpath(from_project_path),
+                            self._getSVGpath())
+        return True
+
+    def GetSVGGeometry(self):
+        # invoke inskscape -S, csv-parse output, produce elements
+        InkscapeGeomColumns = ["Id", "x", "y", "w", "h"]
+
+        inkpath = get_inkscape_path()
+        svgpath = self._getSVGpath()
+        _status, result, _err_result = ProcessLogger(None,
+                                                     inkpath + " -S " + svgpath,
+                                                     no_stdout=True,
+                                                     no_stderr=True).spin()
+        res = []
+        for line in result.split():
+            strippedline = line.strip()
+            attrs = dict(
+                zip(InkscapeGeomColumns, line.strip().split(',')))
+
+            res.append(etree.Element("bbox", **attrs))
+
+        return res
+
+    def GetHMITree(self):
+        global hmi_tree_root
+        res = [hmi_tree_root.etree()]
+        return res
+
+    def CTNGenerate_C(self, buildpath, locations):
+        """
+        Return C code generated by iec2c compiler
+        when _generate_softPLC have been called
+        @param locations: ignored
+        @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND
+        """
+
+        location_str = "_".join(map(str, self.GetCurrentLocation()))
+        view_name = self.BaseParams.getName()
+
+        svgfile = self._getSVGpath()
+
+        res = ([], "", False)
+
+        target_fname = "sghmi_"+location_str+".xhtml"
+
+        target_path = os.path.join(self._getBuildPath(), target_fname)
+        target_file = open(target_path, 'w')
+
+        if os.path.exists(svgfile):
+
+            # TODO : move to __init__
+            transform = XSLTransform(os.path.join(ScriptDirectory, "gen_index_xhtml.xslt"),
+                          [("GetSVGGeometry", lambda *_ignored:self.GetSVGGeometry()),
+                           ("GetHMITree", lambda *_ignored:self.GetHMITree())])
+
+
+            # load svg as a DOM with Etree
+            svgdom = etree.parse(svgfile)
+
+            # call xslt transform on Inkscape's SVG to generate XHTML
+            result = transform.transform(svgdom)
+           
+            result.write(target_file, encoding="utf-8")
+            # print(str(result))
+            # print(transform.xslt.error_log)
+
+            # TODO
+            #   - Errors on HMI semantics
+            #   - ... maybe something to have a global view of what is declared in SVG.
+
+        else:
+            # TODO : use default svg that expose the HMI tree as-is 
+            target_file.write("""<!DOCTYPE html>
+<html>
+<body>
+<h1> No SVG file provided </h1>
+</body>
+</html>
+""")
+
+        target_file.close()
+
+        res += ((target_fname, open(target_path, "rb")),)
+
+        runtimefile_path = os.path.join(buildpath, "runtime_svghmi1_%s.py" % location_str)
+        runtimefile = open(runtimefile_path, 'w')
+        runtimefile.write("""
+def _runtime_svghmi1_%(location)s_start():
+    svghmi_root.putChild('%(view_name)s',File('%(xhtml)s'))
+
+def _runtime_svghmi1_%(location)s_stop():
+    svghmi_root.delEntity('%(view_name)s')
+
+        """ % {"location": location_str,
+               "xhtml": target_fname,
+               "view_name": view_name})
+
+        runtimefile.close()
+
+        res += (("runtime_svghmi1_%s.py" % location_str, open(runtimefile_path, "rb")),)
+
+        return res
+
+    def _ImportSVG(self):
+        dialog = wx.FileDialog(self.GetCTRoot().AppFrame, _("Choose a SVG file"), os.getcwd(), "",  _("SVG files (*.svg)|*.svg|All files|*.*"), wx.OPEN)
+        if dialog.ShowModal() == wx.ID_OK:
+            svgpath = dialog.GetPath()
+            if os.path.isfile(svgpath):
+                shutil.copy(svgpath, self._getSVGpath())
+            else:
+                self.GetCTRoot().logger.write_error(_("No such SVG file: %s\n") % svgpath)
+        dialog.Destroy()
+
+    def _StartInkscape(self):
+        svgfile = self._getSVGpath()
+        open_inkscape = True
+        if not self.GetCTRoot().CheckProjectPathPerm():
+            dialog = wx.MessageDialog(self.GetCTRoot().AppFrame,
+                                      _("You don't have write permissions.\nOpen Inkscape anyway ?"),
+                                      _("Open Inkscape"),
+                                      wx.YES_NO | wx.ICON_QUESTION)
+            open_inkscape = dialog.ShowModal() == wx.ID_YES
+            dialog.Destroy()
+        if open_inkscape:
+            if not os.path.isfile(svgfile):
+                svgfile = None
+            open_svg(svgfile)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/svghmi/svghmi_server.py	Tue Sep 24 11:58:31 2019 +0200
@@ -0,0 +1,138 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# This file is part of Beremiz
+# Copyright (C) 2019: Edouard TISSERANT
+# See COPYING file for copyrights details.
+
+from __future__ import absolute_import
+
+from twisted.web.server import Site
+from twisted.web.resource import Resource
+from twisted.internet import reactor
+from twisted.web.static import File
+
+from autobahn.twisted.websocket import WebSocketServerFactory, WebSocketServerProtocol
+from autobahn.twisted.resource import  WebSocketResource
+
+# TODO multiclient :
+# session list lock
+# svghmi_sessions = []
+
+svghmi_session = None
+
+svghmi_send_collect = PLCBinary.svghmi_send_collect
+svghmi_send_collect.restype = ctypes.c_int # error or 0
+svghmi_send_collect.argtypes = [
+    ctypes.POINTER(ctypes.c_uint32),  # size
+    ctypes.POINTER(ctypes.c_char_p)]  # data ptr
+# TODO multiclient : switch to arrays
+
+svghmi_recv_dispatch = PLCBinary.svghmi_recv_dispatch
+svghmi_recv_dispatch.restype = ctypes.c_int # error or 0
+svghmi_recv_dispatch.argtypes = [
+    ctypes.c_uint32,         # size
+    ctypes.c_char_p]  # data ptr
+# TODO multiclient : switch to arrays
+
+class HMISession(object):
+    def __init__(self, protocol_instance):
+        global svghmi_session
+
+        # TODO: kill existing session for robustness
+        assert(svghmi_session is None)
+
+        svghmi_session = self
+        self.protocol_instance = protocol_instance
+
+        # TODO multiclient :
+        # svghmi_sessions.append(self)
+        # get a unique bit index amont other svghmi_sessions,
+        # so that we can match flags passed by C->python callback
+
+    def __del__(self):
+        global svghmi_session
+        assert(svghmi_session)
+        svghmi_session = None
+
+        # TODO multiclient :
+        # svghmi_sessions.remove(self)
+
+    def onMessage(self, msg):
+        # pass message to the C side recieve_message()
+        c_string = ctypes.c_char_p(msg)
+        c_string_pointer = ctypes.c_void_p(ctypes.addressof(c_string))
+        svghmi_recv_dispatch(len(msg), msg)
+
+        # TODO multiclient : pass client index as well
+
+
+    def sendMessage(self, msg):
+        self.sendMessage(msg, True)
+
+class HMIProtocol(WebSocketServerProtocol):
+
+    def __init__(self, *args, **kwargs):
+        self._hmi_session = None
+        WebSocketServerProtocol.__init__(self, *args, **kwargs)
+
+    def onOpen(self):
+        self._hmi_session = HMISession(self)
+        print "open"
+
+    def onClose(self, wasClean, code, reason):
+        del self._hmi_session
+        self._hmi_session = None
+        print "close"
+
+    def onMessage(self, msg, isBinary):
+        self._hmi_session.onMessage(msg)
+        print msg
+        #self.sendMessage(msg, binary)
+
+class HMIWebSocketServerFactory(WebSocketServerFactory):
+    protocol = HMIProtocol
+
+svghmi_root = None
+svghmi_listener = None
+svghmi_send_thread = None
+
+def SendThreadProc():
+   global svghmi_session
+   size = ctypes.c_uint32()
+   ptr = ctypes.c_char_p()
+   res = 0
+   while svghmi_send_collect(ctypes.byref(size), ctypes.byref(ptr)) == 0 and \
+         svghmi_session is not None and \
+         svghmi_session.sendMessage(ctypes.string_at(ptr,size)) == 0:
+         pass
+
+       # TODO multiclient : dispatch to sessions
+
+
+
+# Called by PLCObject at start
+def _runtime_svghmi0_start():
+    global svghmi_listener, svghmi_root, svghmi_send_thread
+
+    svghmi_root = Resource()
+    svghmi_root.putChild("ws", WebSocketResource(HMIWebSocketServerFactory()))
+
+    svghmi_listener = reactor.listenTCP(8008, Site(svghmi_root))
+
+    # start a thread that call the C part of SVGHMI
+    svghmi_send_thread = Thread(target=SendThreadProc, name="SVGHMI Send")
+    svghmi_send_thread.start()
+
+
+# Called by PLCObject at stop
+def _runtime_svghmi0_stop():
+    global svghmi_listener, svghmi_root, svghmi_send_thread
+    svghmi_root.delEntity("ws")
+    svghmi_root = None
+    svghmi_listener.stopListening()
+    svghmi_listener = None
+    # plc cleanup calls svghmi_(locstring)_cleanup and unlocks send thread
+    svghmi_send_thread.join()
+    svghmi_send_thread = None
+
--- a/targets/Linux/plc_Linux_main_retain.c	Tue Sep 24 11:55:59 2019 +0200
+++ b/targets/Linux/plc_Linux_main_retain.c	Tue Sep 24 11:58:31 2019 +0200
@@ -105,20 +105,20 @@
 /* Compare current hash with hash from file byte by byte.  */
 int CheckFilehash(void)
 {
-	int k;
+	int k,ret;
 	int offset = sizeof(retain_info.retain_size);
 
 	rewind(retain_buffer);
 	fseek(retain_buffer, offset , SEEK_SET);
 
 	uint32_t size;
-	fread(&size, sizeof(size), 1, retain_buffer);
+	ret = fread(&size, sizeof(size), 1, retain_buffer);
 	if (size != retain_info.hash_size)
 		return 0;
 
 	for(k = 0; k < retain_info.hash_size; k++){
 		uint8_t file_digit;
-		fread(&file_digit, sizeof(char), 1, retain_buffer);
+		ret = fread(&file_digit, sizeof(char), 1, retain_buffer);
 		if (file_digit != *(retain_info.hash+k))
 			return 0;
 	}
@@ -317,8 +317,9 @@
 
 void Remind(unsigned int offset, unsigned int count, void *p)
 {
+    int ret;
 	/* Remind variable from file.  */
 	fseek(retain_buffer, retain_info.header_offset+offset, SEEK_SET);
-	fread((void *)p, count, 1, retain_buffer);
+	ret = fread((void *)p, count, 1, retain_buffer);
 }
 #endif // !HAVE_RETAIN
--- a/targets/plc_debug.c	Tue Sep 24 11:55:59 2019 +0200
+++ b/targets/plc_debug.c	Tue Sep 24 11:58:31 2019 +0200
@@ -71,37 +71,9 @@
     }
 }
 
-#define __Unpack_case_t(TYPENAME) \
-        case TYPENAME##_ENUM :\
-            *flags = ((__IEC_##TYPENAME##_t *)varp)->flags;\
-            forced_value_p = *real_value_p = &((__IEC_##TYPENAME##_t *)varp)->value;\
-            break;
-
-#define __Unpack_case_p(TYPENAME)\
-        case TYPENAME##_O_ENUM :\
-            *flags = __IEC_OUTPUT_FLAG;\
-        case TYPENAME##_P_ENUM :\
-            *flags |= ((__IEC_##TYPENAME##_p *)varp)->flags;\
-            *real_value_p = ((__IEC_##TYPENAME##_p *)varp)->value;\
-            forced_value_p = &((__IEC_##TYPENAME##_p *)varp)->fvalue;\
-            break;
-
-void* UnpackVar(dbgvardsc_t *dsc, void **real_value_p, char *flags)
-{
-    void *varp = dsc->ptr;
-    void *forced_value_p = NULL;
-    *flags = 0;
-    /* find data to copy*/
-    switch(dsc->type){
-        __ANY(__Unpack_case_t)
-        __ANY(__Unpack_case_p)
-    default:
-        break;
-    }
-    if (*flags & __IEC_FORCE_FLAG)
-        return forced_value_p;
-    return *real_value_p;
-}
+#define __Unpack_desc_type dbgvardsc_t
+
+%(var_access_code)s
 
 void Remind(unsigned int offset, unsigned int count, void * p);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/targets/var_access.c	Tue Sep 24 11:58:31 2019 +0200
@@ -0,0 +1,33 @@
+
+#define __Unpack_case_t(TYPENAME) \
+        case TYPENAME##_ENUM :\
+            *flags = ((__IEC_##TYPENAME##_t *)varp)->flags;\
+            forced_value_p = *real_value_p = &((__IEC_##TYPENAME##_t *)varp)->value;\
+            break;
+
+#define __Unpack_case_p(TYPENAME)\
+        case TYPENAME##_O_ENUM :\
+            *flags = __IEC_OUTPUT_FLAG;\
+        case TYPENAME##_P_ENUM :\
+            *flags |= ((__IEC_##TYPENAME##_p *)varp)->flags;\
+            *real_value_p = ((__IEC_##TYPENAME##_p *)varp)->value;\
+            forced_value_p = &((__IEC_##TYPENAME##_p *)varp)->fvalue;\
+            break;
+
+static void* UnpackVar(__Unpack_desc_type *dsc, void **real_value_p, char *flags)
+{
+    void *varp = dsc->ptr;
+    void *forced_value_p = NULL;
+    *flags = 0;
+    /* find data to copy*/
+    switch(dsc->type){
+        __ANY(__Unpack_case_t)
+        __ANY(__Unpack_case_p)
+    default:
+        break;
+    }
+    if (*flags & __IEC_FORCE_FLAG)
+        return forced_value_p;
+    return *real_value_p;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/svghmi/beremiz.xml	Tue Sep 24 11:58:31 2019 +0200
@@ -0,0 +1,5 @@
+<?xml version='1.0' encoding='utf-8'?>
+<BeremizRoot xmlns:xsd="http://www.w3.org/2001/XMLSchema" URI_location="LOCAL://">
+  <TargetType/>
+  <Libraries Enable_SVGHMI_Library="true"/>
+</BeremizRoot>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/svghmi/plc.xml	Tue Sep 24 11:58:31 2019 +0200
@@ -0,0 +1,241 @@
+<?xml version='1.0' encoding='utf-8'?>
+<project xmlns:ns1="http://www.plcopen.org/xml/tc6_0201" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.plcopen.org/xml/tc6_0201">
+  <fileHeader companyName="Unknown" productName="Unnamed" productVersion="1" creationDateTime="2019-08-06T14:23:42"/>
+  <contentHeader name="Unnamed" modificationDateTime="2019-09-11T11:54:14">
+    <coordinateInfo>
+      <fbd>
+        <scaling x="5" y="5"/>
+      </fbd>
+      <ld>
+        <scaling x="0" y="0"/>
+      </ld>
+      <sfc>
+        <scaling x="0" y="0"/>
+      </sfc>
+    </coordinateInfo>
+  </contentHeader>
+  <types>
+    <dataTypes/>
+    <pous>
+      <pou name="MainStuff" pouType="program">
+        <interface>
+          <localVars>
+            <variable name="TargetPressure">
+              <type>
+                <derived name="HMI_INT"/>
+              </type>
+            </variable>
+            <variable name="Pump0">
+              <type>
+                <derived name="PumpControl"/>
+              </type>
+            </variable>
+          </localVars>
+        </interface>
+        <body>
+          <FBD>
+            <block localId="2" typeName="ADD" executionOrderId="0" height="60" width="63">
+              <position x="255" y="175"/>
+              <inputVariables>
+                <variable formalParameter="IN1">
+                  <connectionPointIn>
+                    <relPosition x="0" y="30"/>
+                    <connection refLocalId="1">
+                      <position x="255" y="205"/>
+                      <position x="202" y="205"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN2">
+                  <connectionPointIn>
+                    <relPosition x="0" y="50"/>
+                    <connection refLocalId="3">
+                      <position x="255" y="225"/>
+                      <position x="235" y="225"/>
+                      <position x="235" y="235"/>
+                      <position x="190" y="235"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="63" y="30"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inOutVariable localId="1" executionOrderId="0" height="26" width="122" negatedOut="false" negatedIn="false">
+              <position x="120" y="190"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="2" formalParameter="OUT">
+                  <position x="120" y="205"/>
+                  <position x="100" y="205"/>
+                  <position x="100" y="140"/>
+                  <position x="328" y="140"/>
+                  <position x="328" y="205"/>
+                  <position x="318" y="205"/>
+                </connection>
+              </connectionPointIn>
+              <connectionPointOut>
+                <relPosition x="122" y="15"/>
+              </connectionPointOut>
+              <expression>TargetPressure</expression>
+            </inOutVariable>
+            <inVariable localId="3" executionOrderId="0" height="25" width="60" negated="false">
+              <position x="130" y="225"/>
+              <connectionPointOut>
+                <relPosition x="60" y="10"/>
+              </connectionPointOut>
+              <expression>1</expression>
+            </inVariable>
+            <block localId="4" typeName="PumpControl" instanceName="Pump0" executionOrderId="0" height="40" width="127">
+              <position x="595" y="50"/>
+              <inputVariables>
+                <variable formalParameter="TargetPressure">
+                  <connectionPointIn>
+                    <relPosition x="0" y="30"/>
+                    <connection refLocalId="5">
+                      <position x="595" y="80"/>
+                      <position x="570" y="80"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables/>
+            </block>
+            <inVariable localId="5" executionOrderId="0" height="30" width="125" negated="false">
+              <position x="445" y="65"/>
+              <connectionPointOut>
+                <relPosition x="125" y="15"/>
+              </connectionPointOut>
+              <expression>TargetPressure</expression>
+            </inVariable>
+          </FBD>
+        </body>
+      </pou>
+      <pou name="PumpControl" pouType="functionBlock">
+        <interface>
+          <localVars>
+            <variable name="Pump">
+              <type>
+                <derived name="HMI_LABEL"/>
+              </type>
+            </variable>
+            <variable name="Pressure">
+              <type>
+                <derived name="HMI_INT"/>
+              </type>
+            </variable>
+          </localVars>
+          <inputVars>
+            <variable name="TargetPressure">
+              <type>
+                <INT/>
+              </type>
+            </variable>
+          </inputVars>
+          <localVars>
+            <variable name="AddOut">
+              <type>
+                <derived name="HMI_INT"/>
+              </type>
+            </variable>
+          </localVars>
+        </interface>
+        <body>
+          <FBD>
+            <inOutVariable localId="1" executionOrderId="0" height="30" width="75" negatedOut="false" negatedIn="false">
+              <position x="285" y="105"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="2" formalParameter="OUT">
+                  <position x="285" y="120"/>
+                  <position x="275" y="120"/>
+                  <position x="275" y="95"/>
+                  <position x="550" y="95"/>
+                  <position x="550" y="135"/>
+                  <position x="540" y="135"/>
+                </connection>
+              </connectionPointIn>
+              <connectionPointOut>
+                <relPosition x="75" y="15"/>
+              </connectionPointOut>
+              <expression>Pressure</expression>
+            </inOutVariable>
+            <block localId="2" typeName="ADD" executionOrderId="0" height="60" width="65">
+              <position x="475" y="105"/>
+              <inputVariables>
+                <variable formalParameter="IN1">
+                  <connectionPointIn>
+                    <relPosition x="0" y="30"/>
+                    <connection refLocalId="1">
+                      <position x="475" y="135"/>
+                      <position x="417" y="135"/>
+                      <position x="417" y="120"/>
+                      <position x="360" y="120"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN2">
+                  <connectionPointIn>
+                    <relPosition x="0" y="50"/>
+                    <connection refLocalId="3">
+                      <position x="475" y="155"/>
+                      <position x="432" y="155"/>
+                      <position x="432" y="150"/>
+                      <position x="410" y="150"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="65" y="30"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inVariable localId="3" executionOrderId="0" height="25" width="30" negated="false">
+              <position x="380" y="140"/>
+              <connectionPointOut>
+                <relPosition x="30" y="10"/>
+              </connectionPointOut>
+              <expression>23</expression>
+            </inVariable>
+            <outVariable localId="4" executionOrderId="0" height="30" width="60" negated="false">
+              <position x="640" y="135"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="2" formalParameter="OUT">
+                  <position x="640" y="150"/>
+                  <position x="590" y="150"/>
+                  <position x="590" y="135"/>
+                  <position x="540" y="135"/>
+                </connection>
+              </connectionPointIn>
+              <expression>AddOut</expression>
+            </outVariable>
+          </FBD>
+        </body>
+      </pou>
+    </pous>
+  </types>
+  <instances>
+    <configurations>
+      <configuration name="config">
+        <resource name="resource1">
+          <task name="task0" priority="0" interval="T#20ms">
+            <pouInstance name="instance0" typeName="MainStuff"/>
+          </task>
+        </resource>
+      </configuration>
+    </configurations>
+  </instances>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/svghmi/svghmi_0@svghmi/baseconfnode.xml	Tue Sep 24 11:58:31 2019 +0200
@@ -0,0 +1,2 @@
+<?xml version='1.0' encoding='utf-8'?>
+<BaseParams xmlns:xsd="http://www.w3.org/2001/XMLSchema" IEC_Channel="0" Name="svghmi_0"/>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/svghmi/svghmi_0@svghmi/confnode.xml	Tue Sep 24 11:58:31 2019 +0200
@@ -0,0 +1,2 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SVGHMI xmlns:xsd="http://www.w3.org/2001/XMLSchema"/>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/svghmi/svghmi_0@svghmi/svghmi.svg	Tue Sep 24 11:58:31 2019 +0200
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="1280"
+   height="720"
+   viewBox="0 0 1280 720"
+   version="1.1"
+   id="hmi0"
+   sodipodi:docname="gui.svg"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)">
+  <metadata
+     id="metadata4542">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs2" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:document-units="px"
+     inkscape:current-layer="hmi0"
+     showgrid="false"
+     units="px"
+     inkscape:zoom="0.7"
+     inkscape:cx="586.09435"
+     inkscape:cy="381.71289"
+     inkscape:window-width="1600"
+     inkscape:window-height="886"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1" />
+  <rect
+     style="color:#000000"
+     id="page0"
+     width="1280"
+     height="720"
+     x="0"
+     y="0">
+    <desc
+       id="desc_page0">=HMI=
+role = Page;
+name = &quot;Home&quot;;
+</desc>
+  </rect>
+  <text
+     xml:space="preserve"
+     style="font-style:normal;font-weight:normal;font-size:160px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     x="136.32812"
+     y="418.24219"
+     id="text5151"
+     inkscape:label="count"><desc
+       id="desc5153">=HMI=
+role = Input;
+path = &quot;/PRESSURETARGET&quot;;
+/* Don't forget to press the &quot;Set&quot; button ! */</desc><tspan
+       sodipodi:role="line"
+       id="tspan5149"
+       x="136.32812"
+       y="418.24219"
+       style="stroke-width:1px">8888</tspan></text>
+  <text
+     inkscape:label="count"
+     id="text823"
+     y="418.24219"
+     x="756.32812"
+     style="font-style:normal;font-weight:normal;font-size:160px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     xml:space="preserve"><desc
+       id="desc819">=HMI=
+role = Display;
+path = &quot;/PUMP/PRESSURE&quot;;
+/* Don't forget to press the &quot;Set&quot; button ! */</desc><tspan
+       style="stroke-width:1px"
+       y="418.24219"
+       x="756.32812"
+       id="tspan821"
+       sodipodi:role="line">8888</tspan></text>
+  <text
+     xml:space="preserve"
+     style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;"
+     x="242.85715"
+     y="197.14285"
+     id="text827"><tspan
+       sodipodi:role="line"
+       id="tspan825"
+       x="242.85715"
+       y="197.14285">SetPoint</tspan></text>
+  <text
+     id="text831"
+     y="197.14285"
+     x="882.85718"
+     style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#008000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;"
+     xml:space="preserve"><tspan
+       y="197.14285"
+       x="882.85718"
+       id="tspan829"
+       sodipodi:role="line">Actual</tspan></text>
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/yslt_noindent.yml2	Tue Sep 24 11:58:31 2019 +0200
@@ -0,0 +1,40 @@
+include yslt.yml2
+!!
+def indent(level):
+    return ""
+!!
+
+in xsl {
+    decl istylesheet (
+        *output="xml",
+        version="1.0",
+        xmlns:xsl="http://www.w3.org/1999/XSL/Transform",
+        xmlns:exsl='http://exslt.org/common',
+        xmlns:regexp="http://exslt.org/regular-expressions",
+        xmlns:str="http://exslt.org/strings",
+        extension-element-prefixes='exsl regexp str'
+    ) alias stylesheet {
+        output *output;
+        content;
+    };
+
+    decl indent() alias -; 
+
+    decl template(match) {
+        content;
+    };
+
+    decl function(name) alias template {
+        content;
+    };
+   
+    decl call(name) alias call-template {
+        content;
+    };
+
+    decl apply(select) alias apply-templates {
+        content;
+    };
+}
+
+