Merged default in wxPython4, include runtimeLists wxPython4
authorEdouard Tisserant <edouard.tisserant@gmail.com>
Sun, 16 Jan 2022 17:00:58 +0100
branchwxPython4
changeset 3405 fdc12f7d27c8
parent 3391 0ae5a15efa18 (current diff)
parent 3404 6f5cd8d5dc11 (diff)
child 3422 700b39cb4525
Merged default in wxPython4, include runtimeLists
BeremizIDE.py
opc_ua/opcua_client_maker.py
svghmi/svghmi.py
--- a/ProjectController.py	Tue Nov 30 18:43:10 2021 +0100
+++ b/ProjectController.py	Sun Jan 16 17:00:58 2022 +0100
@@ -276,6 +276,7 @@
         # copy StatusMethods so that it can be later customized
         self.StatusMethods = [dic.copy() for dic in self.StatusMethods]
         self.DebugToken = None
+        self.LastComplainDebugToken = None
         self.debug_status = PlcStatus.Stopped
 
         self.IECcodeDigest = None
@@ -797,7 +798,12 @@
 
         IECCodeContent += open(self._getIECgeneratedcodepath(), "r").read()
 
-        with open(self._getIECcodepath(), "w") as plc_file:
+        IECcodepath = self._getIECcodepath()
+
+        if not os.path.exists(IECcodepath):
+            self.LastBuiltIECcodeDigest = None
+
+        with open(IECcodepath, "w") as plc_file:
             plc_file.write(IECCodeContent)
 
         hasher = hashlib.md5()
@@ -966,7 +972,7 @@
                 # describes CSV columns
                 ProgramsListAttributeName = ["num", "C_path", "type"]
                 VariablesListAttributeName = [
-                    "num", "vartype", "IEC_path", "C_path", "type", "derived"]
+                    "num", "vartype", "IEC_path", "C_path", "type", "derived", "retain"]
                 self._ProgramList = []
                 self._VariablesList = []
                 self._DbgVariablesList = []
@@ -1047,10 +1053,9 @@
 
         # prepare debug code
         variable_decl_array = []
-        bofs = 0
-        for v in self._DbgVariablesList:
-            sz = DebugTypesSize.get(v["type"], 0)
-            variable_decl_array += [
+        retain_indexes = []
+        for i, v in enumerate(self._DbgVariablesList):
+            variable_decl_array.append(
                 "{&(%(C_path)s), " % v +
                 {
                     "EXT": "%(type)s_P_ENUM",
@@ -1059,10 +1064,12 @@
                     "OUT": "%(type)s_O_ENUM",
                     "VAR": "%(type)s_ENUM"
                 }[v["vartype"]] % v +
-                "}"]
-            bofs += sz
+                "}")
+
+            if v["retain"] == "1":
+                retain_indexes.append("/* "+v["C_path"]+" */ "+str(i))
+
         debug_code = targets.GetCode("plc_debug.c") % {
-            "buffer_size": bofs,
             "programs_declarations": "\n".join(["extern %(type)s %(C_path)s;" %
                                                 p for p in self._ProgramList]),
             "extern_variables_declarations": "\n".join([
@@ -1076,6 +1083,7 @@
                 }[v["vartype"]] % v
                 for v in self._VariablesList if v["C_path"].find('.') < 0]),
             "variable_decl_array": ",\n".join(variable_decl_array),
+            "retain_vardsc_index_array": ",\n".join(retain_indexes),
             "var_access_code": targets.GetCode("var_access.c")
         }
 
@@ -1550,6 +1558,14 @@
                                     else:
                                         values_buffer.append((value, forced))
                             self.DebugTicks.append(debug_tick)
+                        else:
+                            # complain if trace is incomplete, but only once per debug session
+                            if self.LastComplainDebugToken != self.DebugToken :
+                                self.logger.write_warning(
+                                    _("Debug: target couldn't trace all requested variables.\n"))
+                                self.LastComplainDebugToken = self.DebugToken
+
+
 
         buffers, self.DebugValuesBuffers = (self.DebugValuesBuffers,
                                             [list() for dummy in xrange(len(self.TracedIECPath))])
@@ -1558,6 +1574,15 @@
 
         return debug_status, ticks, buffers
 
+    RegisterDebugVariableErrorCodes = { 
+        # TRACE_LIST_OVERFLOW
+        1 : _("Debug: Too many variables traced. Max 1024.\n"),
+        # FORCE_LIST_OVERFLOW
+        2 : _("Debug: Too many variables forced. Max 256.\n"),
+        # FORCE_BUFFER_OVERFLOW
+        3 : _("Debug: Cumulated forced variables size too large. Max 1KB.\n")
+    }
+
     def RegisterDebugVarToConnector(self):
         Idxs = []
         self.TracedIECPath = []
@@ -1591,7 +1616,14 @@
                 IdxsT = zip(*Idxs)
                 self.TracedIECPath = IdxsT[3]
                 self.TracedIECTypes = IdxsT[1]
-                self.DebugToken = self._connector.SetTraceVariablesList(zip(*IdxsT[0:3]))
+                res = self._connector.SetTraceVariablesList(zip(*IdxsT[0:3]))
+                if res is not None and res > 0:
+                    self.DebugToken = res
+                else:
+                    self.DebugToken = None
+                    self.logger.write_warning(
+                        self.RegisterDebugVariableErrorCodes.get(
+                            -res, _("Debug: Unknown error")))
             else:
                 self.TracedIECPath = []
                 self._connector.SetTraceVariablesList([])
--- a/runtime/PLCObject.py	Tue Nov 30 18:43:10 2021 +0100
+++ b/runtime/PLCObject.py	Sun Jan 16 17:00:58 2022 +0100
@@ -223,7 +223,7 @@
             self._ResetDebugVariables.restype = None
 
             self._RegisterDebugVariable = self.PLClibraryHandle.RegisterDebugVariable
-            self._RegisterDebugVariable.restype = None
+            self._RegisterDebugVariable.restype = ctypes.c_int
             self._RegisterDebugVariable.argtypes = [ctypes.c_int, ctypes.c_void_p]
 
             self._FreeDebugData = self.PLClibraryHandle.FreeDebugData
@@ -294,7 +294,7 @@
         self._startPLC = lambda x, y: None
         self._stopPLC = lambda: None
         self._ResetDebugVariables = lambda: None
-        self._RegisterDebugVariable = lambda x, y: None
+        self._RegisterDebugVariable = lambda x, y: 0
         self._IterDebugData = lambda x, y: None
         self._FreeDebugData = lambda: None
         self._GetDebugData = lambda: -1
@@ -720,7 +720,11 @@
                             TypeTranslator.get(iectype,
                                                (None, None, None))
                         force = ctypes.byref(pack_func(c_type, force))
-                    self._RegisterDebugVariable(idx, force)
+                    res = self._RegisterDebugVariable(idx, force)
+                    if res != 0:
+                        self._resumeDebug()
+                        self._suspendDebug(True)
+                        return -res
                 self._TracesSwap()
                 self._resumeDebug()
                 return self.DebugToken
--- a/runtime/typemapping.py	Tue Nov 30 18:43:10 2021 +0100
+++ b/runtime/typemapping.py	Sun Jan 16 17:00:58 2022 +0100
@@ -85,11 +85,22 @@
     for iectype in indexes:
         c_type, unpack_func, _pack_func = \
             TypeTranslator.get(iectype, (None, None, None))
-        if c_type is not None and buffoffset < buffsize:
-            cursor = c_void_p(buffptr + buffoffset)
+
+        cursor = c_void_p(buffptr + buffoffset)
+        if iectype == "STRING":
+            # strlen is stored in c_uint8 and sizeof(c_uint8) is 1
+            # first check we can read size
+            if (buffoffset + 1) <= buffsize:
+                size = 1 + cast(cursor,POINTER(c_type)).contents.len
+            else:
+                return None
+        else:
+            size = sizeof(c_type)
+
+        if c_type is not None and (buffoffset + size) <= buffsize:
             value = unpack_func(cast(cursor,
                                      POINTER(c_type)).contents)
-            buffoffset += sizeof(c_type) if iectype != "STRING" else len(value)+1
+            buffoffset += size
             res.append(value)
         else:
             return None
--- a/svghmi/analyse_widget.xslt	Tue Nov 30 18:43:10 2021 +0100
+++ b/svghmi/analyse_widget.xslt	Sun Jan 16 17:00:58 2022 +0100
@@ -471,7 +471,7 @@
 </xsl:text>
       <xsl:text>
 </xsl:text>
-      <xsl:text>Documentation to be written. see svbghmi exemple.
+      <xsl:text>Documentation to be written. see svghmi exemple.
 </xsl:text>
     </longdesc>
     <shortdesc>
@@ -566,6 +566,37 @@
     </shortdesc>
     <arg name="listname"/>
   </xsl:template>
+  <xsl:template match="widget[@type='ListSwitch']" mode="widget_desc">
+    <type>
+      <xsl:value-of select="@type"/>
+    </type>
+    <longdesc>
+      <xsl:text>ListSwitch widget displays one item of an HMI:List depending on value of
+</xsl:text>
+      <xsl:text>given variable. Main element of the widget must be a clone of the list or
+</xsl:text>
+      <xsl:text>of an item of that list.  
+</xsl:text>
+      <xsl:text>
+</xsl:text>
+      <xsl:text>Given variable's current value is compared to list items
+</xsl:text>
+      <xsl:text>label. For exemple if given variable type
+</xsl:text>
+      <xsl:text>is HMI_INT and value is 1, then item with label '1' will be displayed.
+</xsl:text>
+      <xsl:text>If matching variable of type HMI_STRING, then no quotes are needed. 
+</xsl:text>
+      <xsl:text>For exemple, 'hello' match HMI_STRING 'hello'.
+</xsl:text>
+    </longdesc>
+    <shortdesc>
+      <xsl:text>Displays item of an HMI:List whose label matches value.</xsl:text>
+    </shortdesc>
+    <path name="value" accepts="HMI_INT,HMI_STRING">
+      <xsl:text>value to compare to labels</xsl:text>
+    </path>
+  </xsl:template>
   <xsl:template match="widget[@type='Meter']" mode="widget_desc">
     <type>
       <xsl:value-of select="@type"/>
@@ -687,7 +718,7 @@
 </xsl:text>
     </longdesc>
     <shortdesc>
-      <xsl:text>Show elements whose label match value.</xsl:text>
+      <xsl:text>Show elements whose label matches value.</xsl:text>
     </shortdesc>
     <path name="value" accepts="HMI_INT,HMI_STRING">
       <xsl:text>value to compare to labels</xsl:text>
--- a/svghmi/detachable_pages.ysl2	Tue Nov 30 18:43:10 2021 +0100
+++ b/svghmi/detachable_pages.ysl2	Sun Jan 16 17:00:58 2022 +0100
@@ -88,7 +88,7 @@
 const "required_page_elements",
     "func:required_elements($hmi_pages | $keypads)/ancestor-or-self::svg:*";
 
-const "required_list_elements", "func:refered_elements(($hmi_lists | $hmi_textlists)[@id = $required_page_elements/@id])";
+const "required_list_elements", "func:refered_elements(($hmi_lists | $hmi_textlists)[@id = $required_page_elements/@id])/ancestor-or-self::svg:*";
 
 const "required_elements", "$defs | $required_list_elements | $required_page_elements";
 
@@ -169,6 +169,7 @@
         if "count($desc/path/@index)=0"
             warning > Page id="«$page/@id»" : No match for path "«$desc/path/@value»" in HMI tree
     |     page_index: «$desc/path/@index»,
+    |     page_class: "«$indexed_hmitree/*[@hmipath = $desc/path/@value]/@class»",
     }
     |     widgets: [
     foreach "$page_managed_widgets" {
--- a/svghmi/gen_index_xhtml.xslt	Tue Nov 30 18:43:10 2021 +0100
+++ b/svghmi/gen_index_xhtml.xslt	Sun Jan 16 17:00:58 2022 +0100
@@ -3,6 +3,7 @@
   <xsl:output cdata-section-elements="xhtml:script" method="xml"/>
   <xsl:variable name="svg" select="/svg:svg"/>
   <xsl:variable name="hmi_elements" select="//svg:*[starts-with(@inkscape:label, 'HMI:')]"/>
+  <xsl:param name="instance_name"/>
   <xsl:variable name="hmitree" select="ns:GetHMITree()"/>
   <xsl:variable name="_categories">
     <noindex>
@@ -39,12 +40,16 @@
 </xsl:text>
     <xsl:text>
 </xsl:text>
+    <xsl:text>var current_page_var_index = </xsl:text>
+    <xsl:value-of select="$indexed_hmitree/*[@hmipath = concat('/CURRENT_PAGE_', $instance_name)]/@index"/>
+    <xsl:text>;
+</xsl:text>
+    <xsl:text>
+</xsl:text>
     <xsl:text>var hmitree_types = [
 </xsl:text>
     <xsl:for-each select="$indexed_hmitree/*">
-      <xsl:text>    /* </xsl:text>
-      <xsl:value-of select="@index"/>
-      <xsl:text> */ "</xsl:text>
+      <xsl:text>    "</xsl:text>
       <xsl:value-of select="substring(local-name(), 5)"/>
       <xsl:text>"</xsl:text>
       <xsl:if test="position()!=last()">
@@ -60,9 +65,7 @@
     <xsl:text>var hmitree_paths = [
 </xsl:text>
     <xsl:for-each select="$indexed_hmitree/*">
-      <xsl:text>    /* </xsl:text>
-      <xsl:value-of select="@index"/>
-      <xsl:text> */ "</xsl:text>
+      <xsl:text>    "</xsl:text>
       <xsl:value-of select="@hmipath"/>
       <xsl:text>"</xsl:text>
       <xsl:if test="position()!=last()">
@@ -75,6 +78,26 @@
 </xsl:text>
     <xsl:text>
 </xsl:text>
+    <xsl:text>var hmitree_nodes = {
+</xsl:text>
+    <xsl:for-each select="$indexed_hmitree/*[local-name() = 'HMI_NODE']">
+      <xsl:text>    "</xsl:text>
+      <xsl:value-of select="@hmipath"/>
+      <xsl:text>" : [</xsl:text>
+      <xsl:value-of select="@index"/>
+      <xsl:text>, "</xsl:text>
+      <xsl:value-of select="@class"/>
+      <xsl:text>"]</xsl:text>
+      <xsl:if test="position()!=last()">
+        <xsl:text>,</xsl:text>
+      </xsl:if>
+      <xsl:text>
+</xsl:text>
+    </xsl:for-each>
+    <xsl:text>};
+</xsl:text>
+    <xsl:text>
+</xsl:text>
     <xsl:text>
 </xsl:text>
   </xsl:template>
@@ -549,7 +572,7 @@
     </xsl:choose>
   </func:function>
   <xsl:variable name="required_page_elements" select="func:required_elements($hmi_pages | $keypads)/ancestor-or-self::svg:*"/>
-  <xsl:variable name="required_list_elements" select="func:refered_elements(($hmi_lists | $hmi_textlists)[@id = $required_page_elements/@id])"/>
+  <xsl:variable name="required_list_elements" select="func:refered_elements(($hmi_lists | $hmi_textlists)[@id = $required_page_elements/@id])/ancestor-or-self::svg:*"/>
   <xsl:variable name="required_elements" select="$defs | $required_list_elements | $required_page_elements"/>
   <xsl:variable name="discardable_elements" select="//svg:*[not(@id = $required_elements/@id)]"/>
   <func:function name="func:sumarized_elements">
@@ -656,6 +679,10 @@
       <xsl:value-of select="$desc/path/@index"/>
       <xsl:text>,
 </xsl:text>
+      <xsl:text>    page_class: "</xsl:text>
+      <xsl:value-of select="$indexed_hmitree/*[@hmipath = $desc/path/@value]/@class"/>
+      <xsl:text>",
+</xsl:text>
     </xsl:if>
     <xsl:text>    widgets: [
 </xsl:text>
@@ -819,7 +846,7 @@
     </xsl:attribute>
   </xsl:template>
   <xsl:variable name="targets_not_to_unlink" select="$hmi_lists/descendant-or-self::svg:*"/>
-  <xsl:variable name="to_unlink" select="$hmi_elements[not(@id = $hmi_pages/@id)]/descendant-or-self::svg:use"/>
+  <xsl:variable name="to_unlink" select="$hmi_widgets/descendant-or-self::svg:use"/>
   <func:function name="func:is_unlinkable">
     <xsl:param name="targetid"/>
     <xsl:param name="eltid"/>
@@ -4556,7 +4583,7 @@
 </xsl:text>
       <xsl:text>
 </xsl:text>
-      <xsl:text>Documentation to be written. see svbghmi exemple.
+      <xsl:text>Documentation to be written. see svghmi exemple.
 </xsl:text>
     </longdesc>
     <shortdesc>
@@ -4806,13 +4833,13 @@
       <xsl:when test="count($from_list) &gt; 0">
         <xsl:text>        id("</xsl:text>
         <xsl:value-of select="@id"/>
-        <xsl:text>").setAttribute("xlink:href",
+        <xsl:text>").href.baseVal =
 </xsl:text>
         <xsl:text>            "#"+hmi_widgets["</xsl:text>
         <xsl:value-of select="$from_list/@id"/>
         <xsl:text>"].items[</xsl:text>
         <xsl:value-of select="$expressions/expression[1]/@content"/>
-        <xsl:text>]);
+        <xsl:text>];
 </xsl:text>
       </xsl:when>
       <xsl:otherwise>
@@ -5648,9 +5675,9 @@
     <xsl:text>    items: {
 </xsl:text>
     <xsl:for-each select="$hmi_element/*[@inkscape:label]">
-      <xsl:text>        </xsl:text>
+      <xsl:text>        "</xsl:text>
       <xsl:value-of select="@inkscape:label"/>
-      <xsl:text>: "</xsl:text>
+      <xsl:text>": "</xsl:text>
       <xsl:value-of select="@id"/>
       <xsl:text>",
 </xsl:text>
@@ -5658,6 +5685,60 @@
     <xsl:text>    },
 </xsl:text>
   </xsl:template>
+  <xsl:template match="widget[@type='ListSwitch']" mode="widget_desc">
+    <type>
+      <xsl:value-of select="@type"/>
+    </type>
+    <longdesc>
+      <xsl:text>ListSwitch widget displays one item of an HMI:List depending on value of
+</xsl:text>
+      <xsl:text>given variable. Main element of the widget must be a clone of the list or
+</xsl:text>
+      <xsl:text>of an item of that list.  
+</xsl:text>
+      <xsl:text>
+</xsl:text>
+      <xsl:text>Given variable's current value is compared to list items
+</xsl:text>
+      <xsl:text>label. For exemple if given variable type
+</xsl:text>
+      <xsl:text>is HMI_INT and value is 1, then item with label '1' will be displayed.
+</xsl:text>
+      <xsl:text>If matching variable of type HMI_STRING, then no quotes are needed. 
+</xsl:text>
+      <xsl:text>For exemple, 'hello' match HMI_STRING 'hello'.
+</xsl:text>
+    </longdesc>
+    <shortdesc>
+      <xsl:text>Displays item of an HMI:List whose label matches value.</xsl:text>
+    </shortdesc>
+    <path name="value" accepts="HMI_INT,HMI_STRING">
+      <xsl:text>value to compare to labels</xsl:text>
+    </path>
+  </xsl:template>
+  <xsl:template match="widget[@type='ListSwitch']" mode="widget_class">
+    <xsl:text>class </xsl:text>
+    <xsl:text>ListSwitchWidget</xsl:text>
+    <xsl:text> extends Widget{
+</xsl:text>
+    <xsl:text>    frequency = 5;
+</xsl:text>
+    <xsl:text>}
+</xsl:text>
+  </xsl:template>
+  <xsl:template match="widget[@type='ListSwitch']" mode="widget_defs">
+    <xsl:param name="hmi_element"/>
+    <xsl:variable name="targetid" select="substring-after($hmi_element/@xlink:href,'#')"/>
+    <xsl:variable name="from_list" select="$hmi_lists[(@id | */@id) = $targetid]"/>
+    <xsl:text>    dispatch: function(value) {
+</xsl:text>
+    <xsl:text>        this.element.href.baseVal = "#"+hmi_widgets["</xsl:text>
+    <xsl:value-of select="$from_list/@id"/>
+    <xsl:text>"].items[value];
+</xsl:text>
+    <xsl:text>    },
+</xsl:text>
+  </xsl:template>
   <xsl:template match="widget[@type='Meter']" mode="widget_desc">
     <type>
       <xsl:value-of select="@type"/>
@@ -7212,7 +7293,7 @@
 </xsl:text>
     </longdesc>
     <shortdesc>
-      <xsl:text>Show elements whose label match value.</xsl:text>
+      <xsl:text>Show elements whose label matches value.</xsl:text>
     </shortdesc>
     <path name="value" accepts="HMI_INT,HMI_STRING">
       <xsl:text>value to compare to labels</xsl:text>
@@ -8045,17 +8126,17 @@
 </xsl:text>
           <xsl:text>// Open WebSocket to relative "/ws" address
 </xsl:text>
+          <xsl:text>var has_watchdog = window.location.hash == "#watchdog";
+</xsl:text>
           <xsl:text>
 </xsl:text>
           <xsl:text>var ws_url = 
 </xsl:text>
           <xsl:text>    window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws')
 </xsl:text>
-          <xsl:text>    + '?mode=' + (window.location.hash == "#watchdog" 
-</xsl:text>
-          <xsl:text>                  ? "watchdog"
-</xsl:text>
-          <xsl:text>                  : "multiclient");
+          <xsl:text>    + '?mode=' + (has_watchdog ? "watchdog" : "multiclient");
+</xsl:text>
+          <xsl:text>
 </xsl:text>
           <xsl:text>var ws = new WebSocket(ws_url);
 </xsl:text>
@@ -8375,23 +8456,49 @@
 </xsl:text>
           <xsl:text>
 </xsl:text>
-          <xsl:text>// artificially subscribe the watchdog widget to "/heartbeat" hmi variable
-</xsl:text>
-          <xsl:text>// Since dispatch directly calls change_hmi_value,
-</xsl:text>
-          <xsl:text>// PLC will periodically send variable at given frequency
-</xsl:text>
-          <xsl:text>subscribers(heartbeat_index).add({
-</xsl:text>
-          <xsl:text>    /* type: "Watchdog", */
+          <xsl:text>if(has_watchdog){
+</xsl:text>
+          <xsl:text>    // artificially subscribe the watchdog widget to "/heartbeat" hmi variable
+</xsl:text>
+          <xsl:text>    // Since dispatch directly calls change_hmi_value,
+</xsl:text>
+          <xsl:text>    // PLC will periodically send variable at given frequency
+</xsl:text>
+          <xsl:text>    subscribers(heartbeat_index).add({
+</xsl:text>
+          <xsl:text>        /* type: "Watchdog", */
+</xsl:text>
+          <xsl:text>        frequency: 1,
+</xsl:text>
+          <xsl:text>        indexes: [heartbeat_index],
+</xsl:text>
+          <xsl:text>        new_hmi_value: function(index, value, oldval) {
+</xsl:text>
+          <xsl:text>            apply_hmi_value(heartbeat_index, value+1);
+</xsl:text>
+          <xsl:text>        }
+</xsl:text>
+          <xsl:text>    });
+</xsl:text>
+          <xsl:text>}
+</xsl:text>
+          <xsl:text>
+</xsl:text>
+          <xsl:text>// subscribe to per instance current page hmi variable
+</xsl:text>
+          <xsl:text>// PLC must prefix page name with "!" for page switch to happen
+</xsl:text>
+          <xsl:text>subscribers(current_page_var_index).add({
 </xsl:text>
           <xsl:text>    frequency: 1,
 </xsl:text>
-          <xsl:text>    indexes: [heartbeat_index],
+          <xsl:text>    indexes: [current_page_var_index],
 </xsl:text>
           <xsl:text>    new_hmi_value: function(index, value, oldval) {
 </xsl:text>
-          <xsl:text>        apply_hmi_value(heartbeat_index, value+1);
+          <xsl:text>        if(value.startsWith("!"))
+</xsl:text>
+          <xsl:text>            switch_page(value.slice(1));
 </xsl:text>
           <xsl:text>    }
 </xsl:text>
@@ -8787,7 +8894,11 @@
 </xsl:text>
           <xsl:text>        page_name = current_subscribed_page;
 </xsl:text>
-          <xsl:text>
+          <xsl:text>    else if(page_index == undefined){
+</xsl:text>
+          <xsl:text>        [page_name, page_index] = page_name.split('@')
+</xsl:text>
+          <xsl:text>    }
 </xsl:text>
           <xsl:text>
 </xsl:text>
@@ -8807,10 +8918,32 @@
 </xsl:text>
           <xsl:text>
 </xsl:text>
-          <xsl:text>    if(page_index == undefined){
+          <xsl:text>    if(page_index == undefined)
 </xsl:text>
           <xsl:text>        page_index = new_desc.page_index;
 </xsl:text>
+          <xsl:text>    else if(typeof(page_index) == "string") {
+</xsl:text>
+          <xsl:text>        let hmitree_node = hmitree_nodes[page_index];
+</xsl:text>
+          <xsl:text>        if(hmitree_node !== undefined){
+</xsl:text>
+          <xsl:text>            let [int_index, hmiclass] = hmitree_node;
+</xsl:text>
+          <xsl:text>            if(hmiclass == new_desc.page_class)
+</xsl:text>
+          <xsl:text>                page_index = int_index;
+</xsl:text>
+          <xsl:text>            else
+</xsl:text>
+          <xsl:text>                page_index = new_desc.page_index;
+</xsl:text>
+          <xsl:text>        } else {
+</xsl:text>
+          <xsl:text>            page_index = new_desc.page_index;
+</xsl:text>
+          <xsl:text>        }
+</xsl:text>
           <xsl:text>    }
 </xsl:text>
           <xsl:text>
@@ -8871,6 +9004,14 @@
 </xsl:text>
           <xsl:text>
 </xsl:text>
+          <xsl:text>    apply_hmi_value(current_page_var_index, page_index == undefined
+</xsl:text>
+          <xsl:text>        ? page_name
+</xsl:text>
+          <xsl:text>        : page_name + "@" + hmitree_paths[page_index]);
+</xsl:text>
+          <xsl:text>
+</xsl:text>
           <xsl:text>    return true;
 </xsl:text>
           <xsl:text>};
--- a/svghmi/hmi_tree.py	Tue Nov 30 18:43:10 2021 +0100
+++ b/svghmi/hmi_tree.py	Sun Jan 16 17:00:58 2022 +0100
@@ -160,5 +160,4 @@
 
 SPECIAL_NODES = [("HMI_ROOT", "HMI_NODE"),
                  ("heartbeat", "HMI_INT")]
-                 # ("current_page", "HMI_STRING")])
 
--- a/svghmi/hmi_tree.ysl2	Tue Nov 30 18:43:10 2021 +0100
+++ b/svghmi/hmi_tree.ysl2	Sun Jan 16 17:00:58 2022 +0100
@@ -1,5 +1,7 @@
 // hmi_tree.ysl2
 
+// Location identifies uniquely SVGHMI instance
+param "instance_name";
 
 // HMI Tree computed from VARIABLES.CSV in svghmi.py
 const "hmitree", "ns:GetHMITree()";
@@ -19,20 +21,28 @@
     |
     | var heartbeat_index = «$indexed_hmitree/*[@hmipath = '/HEARTBEAT']/@index»;
     |
+    | var current_page_var_index = «$indexed_hmitree/*[@hmipath = concat('/CURRENT_PAGE_', $instance_name)]/@index»;
+    |
     | var hmitree_types = [
 
     foreach "$indexed_hmitree/*" 
-    |     /* «@index» */ "«substring(local-name(), 5)»"`if "position()!=last()" > ,`
+    |     "«substring(local-name(), 5)»"`if "position()!=last()" > ,`
 
     | ];
     |
     | var hmitree_paths = [
 
     foreach "$indexed_hmitree/*" 
-    |     /* «@index» */ "«@hmipath»"`if "position()!=last()" > ,`
+    |     "«@hmipath»"`if "position()!=last()" > ,`
 
     | ];
     |
+    | var hmitree_nodes = {
+
+    foreach "$indexed_hmitree/*[local-name() = 'HMI_NODE']" 
+    |     "«@hmipath»" : [«@index», "«@class»"]`if "position()!=last()" > ,`
+    | };
+    |
 }
 
 template "*", mode="index" {
--- a/svghmi/inline_svg.ysl2	Tue Nov 30 18:43:10 2021 +0100
+++ b/svghmi/inline_svg.ysl2	Sun Jan 16 17:00:58 2022 +0100
@@ -56,7 +56,7 @@
 // TODO: narrow application of clone unlinking to active elements,
 //       while keeping static decoration cloned
 const "targets_not_to_unlink", "$hmi_lists/descendant-or-self::svg:*";
-const "to_unlink", "$hmi_elements[not(@id = $hmi_pages/@id)]/descendant-or-self::svg:use";
+const "to_unlink", "$hmi_widgets/descendant-or-self::svg:use";
 
 def "func:is_unlinkable" {
     param "targetid";
@@ -174,7 +174,7 @@
     param "seed";
     choose {
         // node recursive copy ends when finding a widget
-        when "@id = $hmi_elements/@id" {
+        when "@id = $hmi_widgets/@id" {
             // place a clone instead of copying
             use{
                 attrib "xlink:href" > «concat('#',@id)»
--- a/svghmi/svghmi.c	Tue Nov 30 18:43:10 2021 +0100
+++ b/svghmi/svghmi.c	Sun Jan 16 17:00:58 2022 +0100
@@ -10,6 +10,7 @@
 #define HMI_ITEM_COUNT %(item_count)d
 #define HMI_HASH_SIZE 8
 #define MAX_CONNECTIONS %(max_connections)d
+#define MAX_CON_INDEX MAX_CONNECTIONS - 1
 
 static uint8_t hmi_hash[HMI_HASH_SIZE] = {%(hmi_hash_ints)s};
 
@@ -19,7 +20,6 @@
 /* PLC writes to that buffer */
 static char wbuf[HMI_BUFFER_SIZE];
 
-/* TODO change that in case of multiclient... */
 /* worst biggest send buffer. FIXME : use dynamic alloc ? */
 static char sbuf[HMI_HASH_SIZE +  HMI_BUFFER_SIZE + (HMI_ITEM_COUNT * sizeof(uint32_t))];
 static unsigned int sbufidx;
@@ -42,11 +42,15 @@
 static long hmitree_rlock = 0;
 static long hmitree_wlock = 0;
 
-typedef struct {
+typedef struct hmi_tree_item_s hmi_tree_item_t;
+struct hmi_tree_item_s{
     void *ptr;
     __IEC_types_enum type;
     uint32_t buf_index;
 
+    /* retrieve/read/recv */
+    buf_state_t rstate;
+
     /* publish/write/send */
     buf_state_t wstate[MAX_CONNECTIONS];
 
@@ -54,111 +58,114 @@
     uint16_t refresh_period_ms[MAX_CONNECTIONS];
     uint16_t age_ms[MAX_CONNECTIONS];
 
-    /* retrieve/read/recv */
-    buf_state_t rstate;
-
-} hmi_tree_item_t;
-
-static hmi_tree_item_t hmi_tree_item[] = {
+    /* dual linked list for subscriptions */
+    hmi_tree_item_t *subscriptions_next;
+    hmi_tree_item_t *subscriptions_prev;
+
+    /* single linked list for changes from HMI */
+    hmi_tree_item_t *incoming_prev;
+
+};
+
+#define HMITREE_ITEM_INITIALIZER(cpath,type,buf_index) {        \
+    &(cpath),                             /*ptr*/               \
+    type,                                 /*type*/              \
+    buf_index,                            /*buf_index*/         \
+    buf_free,                             /*rstate*/            \
+    {[0 ... MAX_CON_INDEX] = buf_free},   /*wstate*/            \
+    {[0 ... MAX_CON_INDEX] = 0},          /*refresh_period_ms*/ \
+    {[0 ... MAX_CON_INDEX] = 0},          /*age_ms*/            \
+    NULL,                                 /*subscriptions_next*/\
+    NULL,                                 /*subscriptions_prev*/\
+    NULL}                                 /*incoming_next*/
+
+
+/* entry for dual linked list for HMI subscriptions */
+/* points to the end of the list */
+static hmi_tree_item_t  *subscriptions_tail = NULL;
+
+/* entry for single linked list for changes from HMI */
+/* points to the end of the list */
+static hmi_tree_item_t *incoming_tail = NULL;
+
+static hmi_tree_item_t hmi_tree_items[] = {
 %(variable_decl_array)s
 };
 
-typedef int(*hmi_tree_iterator)(uint32_t, hmi_tree_item_t*);
-static int 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];
-        int res = (*fp)(i, dsc);
-        if(res != 0){
-            return res;
-        }
-    }
+#define __Unpack_desc_type hmi_tree_item_t
+
+%(var_access_code)s
+
+static int write_iterator(hmi_tree_item_t *dsc)
+{
+    uint32_t session_index = 0;
+    int value_changed = 0;
+    void *dest_p = NULL;
+    void *value_p = NULL;
+    size_t sz = 0;
+    while(session_index < MAX_CONNECTIONS) {
+        if(dsc->wstate[session_index] == buf_set){
+            /* if being subscribed */
+            if(dsc->refresh_period_ms[session_index]){
+                if(dsc->age_ms[session_index] + ticktime_ms < dsc->refresh_period_ms[session_index]){
+                    dsc->age_ms[session_index] += ticktime_ms;
+                }else{
+                    dsc->wstate[session_index] = buf_tosend;
+                    global_write_dirty = 1;
+                }
+            }
+        }
+
+        /* variable is sample only if just subscribed
+           or already subscribed and having value change */
+        int do_sample = 0;
+        int just_subscribed = dsc->wstate[session_index] == buf_new;
+        if(!just_subscribed){
+            int already_subscribed = dsc->refresh_period_ms[session_index] > 0;
+            if(already_subscribed){
+                if(!value_changed){
+                    if(!value_p){
+                        UnpackVar(dsc, &value_p, NULL, &sz);
+                        if(__Is_a_string(dsc)){
+                            sz = ((STRING*)value_p)->len + 1;
+                        }
+                        dest_p = &wbuf[dsc->buf_index];
+                    }
+                    value_changed = memcmp(dest_p, value_p, sz) != 0;
+                    do_sample = value_changed;
+                }else{
+                    do_sample = 1;
+                }
+            }
+        } else {
+            do_sample = 1;
+        }
+
+
+        if(do_sample){
+            if(dsc->wstate[session_index] != buf_set && dsc->wstate[session_index] != buf_tosend) {
+                if(dsc->wstate[session_index] == buf_new \
+                   || ticktime_ms > dsc->refresh_period_ms[session_index]){
+                    dsc->wstate[session_index] = buf_tosend;
+                    global_write_dirty = 1;
+                } else {
+                    dsc->wstate[session_index] = buf_set;
+                }
+                dsc->age_ms[session_index] = 0;
+            }
+        }
+
+        session_index++;
+    }
+    /* copy value if changed (and subscribed) */
+    if(value_changed)
+        memcpy(dest_p, value_p, sz);
     return 0;
 }
 
-#define __Unpack_desc_type hmi_tree_item_t
-
-%(var_access_code)s
-
-static int write_iterator(uint32_t index, hmi_tree_item_t *dsc)
-{
-    {
-        uint32_t session_index = 0;
-        int value_changed = 0;
-        void *dest_p = NULL;
-        void *real_value_p = NULL;
-        void *visible_value_p = NULL;
-        USINT sz = 0;
-        while(session_index < MAX_CONNECTIONS) {
-            if(dsc->wstate[session_index] == buf_set){
-                /* if being subscribed */
-                if(dsc->refresh_period_ms[session_index]){
-                    if(dsc->age_ms[session_index] + ticktime_ms < dsc->refresh_period_ms[session_index]){
-                        dsc->age_ms[session_index] += ticktime_ms;
-                    }else{
-                        dsc->wstate[session_index] = buf_tosend;
-                        global_write_dirty = 1;
-                    }
-                }
-            }
-
-            /* variable is sample only if just subscribed
-               or already subscribed and having value change */
-            int do_sample = 0;
-            int just_subscribed = dsc->wstate[session_index] == buf_new;
-            if(!just_subscribed){
-                int already_subscribed = dsc->refresh_period_ms[session_index] > 0;
-                if(already_subscribed){
-                    if(!value_changed){
-                        if(!visible_value_p){
-                            char flags = 0;
-                            visible_value_p = UnpackVar(dsc, &real_value_p, &flags);
-                            if(__Is_a_string(dsc)){
-                                sz = ((STRING*)visible_value_p)->len + 1;
-                            } else {
-                                sz = __get_type_enum_size(dsc->type);
-                            }
-                            dest_p = &wbuf[dsc->buf_index];
-                        }
-                        value_changed = memcmp(dest_p, visible_value_p, sz) != 0;
-                        do_sample = value_changed;
-                    }else{
-                        do_sample = 1;
-                    }
-                }
-            } else {
-                do_sample = 1;
-            }
-
-
-            if(do_sample){
-                if(dsc->wstate[session_index] != buf_set && dsc->wstate[session_index] != buf_tosend) {
-                    if(dsc->wstate[session_index] == buf_new \
-                       || ticktime_ms > dsc->refresh_period_ms[session_index]){
-                        dsc->wstate[session_index] = buf_tosend;
-                        global_write_dirty = 1;
-                    } else {
-                        dsc->wstate[session_index] = buf_set;
-                    }
-                    dsc->age_ms[session_index] = 0;
-                }
-            }
-
-            session_index++;
-        }
-        /* copy value if changed (and subscribed) */
-        if(value_changed)
-            memcpy(dest_p, visible_value_p, sz);
-    }
-    // else ... : PLC can't wait, variable will be updated next turn
-    return 0;
-}
-
-static uint32_t send_session_index;
-static int send_iterator(uint32_t index, hmi_tree_item_t *dsc)
-{
-    if(dsc->wstate[send_session_index] == buf_tosend)
+static int send_iterator(uint32_t index, hmi_tree_item_t *dsc, uint32_t session_index)
+{
+    if(dsc->wstate[session_index] == buf_tosend)
     {
         uint32_t sz = __get_type_enum_size(dsc->type);
         if(sbufidx + sizeof(uint32_t) + sz <=  sizeof(sbuf))
@@ -171,7 +178,7 @@
             /* TODO : force into little endian */
             memcpy(dst_p, &index, sizeof(uint32_t));
             memcpy(dst_p + sizeof(uint32_t), src_p, sz);
-            dsc->wstate[send_session_index] = buf_free;
+            dsc->wstate[session_index] = buf_free;
             sbufidx += sizeof(uint32_t) /* index */ + sz;
         }
         else
@@ -184,15 +191,15 @@
     return 0;
 }
 
-static int read_iterator(uint32_t index, hmi_tree_item_t *dsc)
+static int read_iterator(hmi_tree_item_t *dsc)
 {
     if(dsc->rstate == buf_set)
     {
         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(real_value_p, src_p, __get_type_enum_size(dsc->type));
+        void *value_p = NULL;
+        size_t sz = 0;
+        UnpackVar(dsc, &value_p, NULL, &sz);
+        memcpy(value_p, src_p, sz);
         dsc->rstate = buf_free;
     }
     return 0;
@@ -200,24 +207,64 @@
 
 void update_refresh_period(hmi_tree_item_t *dsc, uint32_t session_index, uint16_t refresh_period_ms)
 {
-    if(refresh_period_ms) {
-        if(!dsc->refresh_period_ms[session_index])
+    uint32_t other_session_index = 0;
+    int previously_subscribed = 0;
+    int session_only_subscriber = 0;
+    int session_already_subscriber = 0;
+    int needs_subscription_for_session = (refresh_period_ms != 0);
+
+    while(other_session_index < session_index) {
+        previously_subscribed |= (dsc->refresh_period_ms[other_session_index++] != 0);
+    }
+    session_already_subscriber = (dsc->refresh_period_ms[other_session_index++] != 0);
+    while(other_session_index < MAX_CONNECTIONS) {
+        previously_subscribed |= (dsc->refresh_period_ms[other_session_index++] != 0);
+    }
+    session_only_subscriber = session_already_subscriber && !previously_subscribed;
+    previously_subscribed |= session_already_subscriber;
+
+    if(needs_subscription_for_session) {
+        if(!session_already_subscriber)
         {
             dsc->wstate[session_index] = buf_new;
         }
+        /* item is appended to list only when no session was previously subscribed */
+        if(!previously_subscribed){
+            /* append subsciption to list */
+            if(subscriptions_tail != NULL){ 
+                /* if list wasn't empty, link with previous tail*/
+                subscriptions_tail->subscriptions_next = dsc;
+            }
+            dsc->subscriptions_prev = subscriptions_tail;
+            subscriptions_tail = dsc;
+            dsc->subscriptions_next = NULL;
+        }
     } else {
         dsc->wstate[session_index] = buf_free;
+        /* item is removed from list only when session was the only one remaining */
+        if (session_only_subscriber) {
+            if(dsc->subscriptions_next == NULL){ /* remove tail  */
+                /* re-link tail to previous */
+                subscriptions_tail = dsc->subscriptions_prev;
+                if(subscriptions_tail != NULL){
+                    subscriptions_tail->subscriptions_next = NULL;
+                }
+            } else if(dsc->subscriptions_prev == NULL){ /* remove head  */
+                dsc->subscriptions_next->subscriptions_prev = NULL;
+            } else { /* remove entry in between other entries */
+                /* re-link previous and next node */
+                dsc->subscriptions_next->subscriptions_prev = dsc->subscriptions_prev;
+                dsc->subscriptions_prev->subscriptions_next = dsc->subscriptions_next;
+            }
+            /* unnecessary
+            dsc->subscriptions_next = NULL;
+            dsc->subscriptions_prev = NULL;
+            */
+        }
     }
     dsc->refresh_period_ms[session_index] = refresh_period_ms;
 }
 
-static uint32_t reset_session_index;
-static int reset_iterator(uint32_t index, hmi_tree_item_t *dsc)
-{
-    update_refresh_period(dsc, reset_session_index, 0);
-    return 0;
-}
-
 static void *svghmi_handle;
 
 void SVGHMI_SuspendFromPythonThread(void)
@@ -242,7 +289,7 @@
     /* create svghmi_pipe */
     svghmi_handle = create_RT_to_nRT_signal("SVGHMI_pipe");
 
-    if(!svghmi_handle) 
+    if(!svghmi_handle)
         return 1;
 
     return 0;
@@ -258,7 +305,18 @@
 void __retrieve_svghmi()
 {
     if(AtomicCompareExchange(&hmitree_rlock, 0, 1) == 0) {
-        traverse_hmi_tree(read_iterator);
+        hmi_tree_item_t *dsc = incoming_tail;
+        /* iterate through read list (changes from HMI) */
+        while(dsc){
+            hmi_tree_item_t *_dsc = dsc->incoming_prev;
+            read_iterator(dsc);
+            /* unnecessary
+            dsc->incoming_prev = NULL;
+            */
+            dsc = _dsc;
+        }
+        /* flush read list */
+        incoming_tail = NULL;
         AtomicCompareExchange(&hmitree_rlock, 1, 0);
     }
 }
@@ -266,10 +324,16 @@
 void __publish_svghmi()
 {
     global_write_dirty = 0;
+
     if(AtomicCompareExchange(&hmitree_wlock, 0, 1) == 0) {
-        traverse_hmi_tree(write_iterator);
+        hmi_tree_item_t *dsc = subscriptions_tail;
+        while(dsc){
+            write_iterator(dsc);
+            dsc = dsc->subscriptions_prev;
+        }
         AtomicCompareExchange(&hmitree_wlock, 1, 0);
     }
+
     if(global_write_dirty) {
         SVGHMI_WakeupFromRTThread();
     }
@@ -283,16 +347,25 @@
 
 int svghmi_send_collect(uint32_t session_index, uint32_t *size, char **ptr){
 
+
     if(svghmi_continue_collect) {
         int res;
         sbufidx = HMI_HASH_SIZE;
-        send_session_index = session_index;
 
         while(AtomicCompareExchange(&hmitree_wlock, 0, 1)){
             nRT_reschedule();
         }
 
-        if((res = traverse_hmi_tree(send_iterator)) == 0)
+        hmi_tree_item_t *dsc = subscriptions_tail;
+        while(dsc){
+            uint32_t index = dsc - hmi_tree_items;
+            res = send_iterator(index, dsc, session_index);
+            if(res != 0){
+                break;
+            }
+            dsc = dsc->subscriptions_prev;
+        }
+        if(res == 0)
         {
             if(sbufidx > HMI_HASH_SIZE){
                 memcpy(&sbuf[0], &hmi_hash[0], HMI_HASH_SIZE);
@@ -304,7 +377,6 @@
             AtomicCompareExchange(&hmitree_wlock, 1, 0);
             return ENODATA;
         }
-        // printf("collected BAD result %%d\n", res);
         AtomicCompareExchange(&hmitree_wlock, 1, 0);
         return res;
     }
@@ -322,8 +394,16 @@
 } cmd_from_JS;
 
 int svghmi_reset(uint32_t session_index){
-    reset_session_index = session_index;
-    traverse_hmi_tree(reset_iterator);
+    hmi_tree_item_t *dsc = subscriptions_tail;
+    while(AtomicCompareExchange(&hmitree_wlock, 0, 1)){
+        nRT_reschedule();
+    }
+    while(dsc){
+        hmi_tree_item_t *_dsc = dsc->subscriptions_prev;
+        update_refresh_period(dsc, session_index, 0);
+        dsc = _dsc;
+    }
+    AtomicCompareExchange(&hmitree_wlock, 1, 0);
     return 1;
 }
 
@@ -355,6 +435,7 @@
         cmd_old = cmd;
         cmd = *(cursor++);
 
+
         if(cmd_old != cmd){
             if(got_wlock){
                 AtomicCompareExchange(&hmitree_wlock, 1, 0);
@@ -372,20 +453,20 @@
                 uint32_t index = *(uint32_t*)(cursor);
                 uint8_t const *valptr = cursor + sizeof(uint32_t);
 
+
                 if(index == heartbeat_index)
                     was_hearbeat = 1;
 
                 if(index < HMI_ITEM_COUNT)
                 {
-                    hmi_tree_item_t *dsc = &hmi_tree_item[index];
-                    void *real_value_p = NULL;
-                    char flags = 0;
-                    void *visible_value_p = UnpackVar(dsc, &real_value_p, &flags);
+                    hmi_tree_item_t *dsc = &hmi_tree_items[index];
+                    size_t sz = 0;
                     void *dst_p = &rbuf[dsc->buf_index];
-                    uint32_t sz = __get_type_enum_size(dsc->type);
 
                     if(__Is_a_string(dsc)){
                         sz = ((STRING*)valptr)->len + 1;
+                    } else {
+                        UnpackVar(dsc, NULL, NULL, &sz);
                     }
 
                     if((valptr + sz) <= end)
@@ -399,7 +480,14 @@
                         }
 
                         memcpy(dst_p, valptr, sz);
-                        dsc->rstate = buf_set;
+
+                        /* check that rstate is not already buf_set */
+                        if(dsc->rstate != buf_set){
+                            dsc->rstate = buf_set;
+                            /* append entry to read list (changes from HMI) */
+                            dsc->incoming_prev = incoming_tail;
+                            incoming_tail = dsc;
+                        }
 
                         progress = sz + sizeof(uint32_t) /* index */;
                     }
@@ -420,14 +508,20 @@
             case reset:
             {
                 progress = 0;
-                reset_session_index = session_index;
                 if(!got_wlock){
                     while(AtomicCompareExchange(&hmitree_wlock, 0, 1)){
                         nRT_reschedule();
                     }
                     got_wlock = 1;
                 }
-                traverse_hmi_tree(reset_iterator);
+                {
+                    hmi_tree_item_t *dsc = subscriptions_tail;
+                    while(dsc){
+                        hmi_tree_item_t *_dsc = dsc->subscriptions_prev;
+                        update_refresh_period(dsc, session_index, 0);
+                        dsc = _dsc;
+                    }
+                }
             }
             break;
 
@@ -444,7 +538,7 @@
                         }
                         got_wlock = 1;
                     }
-                    hmi_tree_item_t *dsc = &hmi_tree_item[index];
+                    hmi_tree_item_t *dsc = &hmi_tree_items[index];
                     update_refresh_period(dsc, session_index, refresh_period_ms);
                 }
                 else
--- a/svghmi/svghmi.js	Tue Nov 30 18:43:10 2021 +0100
+++ b/svghmi/svghmi.js	Sun Jan 16 17:00:58 2022 +0100
@@ -30,12 +30,12 @@
 };
 
 // Open WebSocket to relative "/ws" address
+var has_watchdog = window.location.hash == "#watchdog";
 
 var ws_url = 
     window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws')
-    + '?mode=' + (window.location.hash == "#watchdog" 
-                  ? "watchdog"
-                  : "multiclient");
+    + '?mode=' + (has_watchdog ? "watchdog" : "multiclient");
+
 var ws = new WebSocket(ws_url);
 ws.binaryType = 'arraybuffer';
 
@@ -195,15 +195,28 @@
     }
 }
 
-// artificially subscribe the watchdog widget to "/heartbeat" hmi variable
-// Since dispatch directly calls change_hmi_value,
-// PLC will periodically send variable at given frequency
-subscribers(heartbeat_index).add({
-    /* type: "Watchdog", */
+if(has_watchdog){
+    // artificially subscribe the watchdog widget to "/heartbeat" hmi variable
+    // Since dispatch directly calls change_hmi_value,
+    // PLC will periodically send variable at given frequency
+    subscribers(heartbeat_index).add({
+        /* type: "Watchdog", */
+        frequency: 1,
+        indexes: [heartbeat_index],
+        new_hmi_value: function(index, value, oldval) {
+            apply_hmi_value(heartbeat_index, value+1);
+        }
+    });
+}
+
+// subscribe to per instance current page hmi variable
+// PLC must prefix page name with "!" for page switch to happen
+subscribers(current_page_var_index).add({
     frequency: 1,
-    indexes: [heartbeat_index],
+    indexes: [current_page_var_index],
     new_hmi_value: function(index, value, oldval) {
-        apply_hmi_value(heartbeat_index, value+1);
+        if(value.startsWith("!"))
+            switch_page(value.slice(1));
     }
 });
 
@@ -401,7 +414,9 @@
 
     if(page_name == undefined)
         page_name = current_subscribed_page;
-
+    else if(page_index == undefined){
+        [page_name, page_index] = page_name.split('@')
+    }
 
     let old_desc = page_desc[current_subscribed_page];
     let new_desc = page_desc[page_name];
@@ -411,8 +426,19 @@
         return false;
     }
 
-    if(page_index == undefined){
+    if(page_index == undefined)
         page_index = new_desc.page_index;
+    else if(typeof(page_index) == "string") {
+        let hmitree_node = hmitree_nodes[page_index];
+        if(hmitree_node !== undefined){
+            let [int_index, hmiclass] = hmitree_node;
+            if(hmiclass == new_desc.page_class)
+                page_index = int_index;
+            else
+                page_index = new_desc.page_index;
+        } else {
+            page_index = new_desc.page_index;
+        }
     }
 
     if(old_desc){
@@ -443,6 +469,10 @@
     if(jump_history.length > 42)
         jump_history.shift();
 
+    apply_hmi_value(current_page_var_index, page_index == undefined
+        ? page_name
+        : page_name + "@" + hmitree_paths[page_index]);
+
     return true;
 };
 
--- a/svghmi/svghmi.py	Tue Nov 30 18:43:10 2021 +0100
+++ b/svghmi/svghmi.py	Sun Jan 16 17:00:58 2022 +0100
@@ -130,6 +130,10 @@
             # ignores variables starting with _TMP_
             if path[-1].startswith("_TMP_"):
                 continue
+            vartype = v["vartype"]
+            # ignores external variables
+            if vartype == "EXT":
+                continue
             derived = v["derived"]
             kwargs={}
             if derived == "HMI_NODE":
@@ -138,7 +142,7 @@
                 kwargs['hmiclass'] = path[-1]
             else:
                 name = path[-1]
-            new_node = HMITreeNode(path, name, derived, v["type"], v["vartype"], v["C_path"], **kwargs)
+            new_node = HMITreeNode(path, name, derived, v["type"], vartype, v["C_path"], **kwargs)
             placement_result = hmi_tree_root.place_node(new_node)
             if placement_result is not None:
                 cause, problematic_node = placement_result
@@ -148,10 +152,10 @@
                         ".".join(new_node.path))
 
                     last_FB = None 
-                    for v in varlist:
-                        if v["vartype"] == "FB":
-                            last_FB = v 
-                        if v["C_path"] == problematic_node:
+                    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"]
@@ -187,14 +191,14 @@
             if hasattr(node, "iectype"):
                 sz = DebugTypesSize.get(node.iectype, 0)
                 variable_decl_array += [
-                    "{&(" + node.cpath + "), " + node.iectype + {
+                    "HMITREE_ITEM_INITIALIZER(" + node.cpath + ", " + node.iectype + {
                         "EXT": "_P_ENUM",
                         "IN":  "_P_ENUM",
                         "MEM": "_O_ENUM",
                         "OUT": "_O_ENUM",
                         "VAR": "_ENUM"
                     }[node.vartype] + ", " +
-                    str(buf_index) + ", 0, }"]
+                    str(buf_index) + ")"]
                 buf_index += sz
                 item_count += 1
                 if len(node.path) == 1:
@@ -572,7 +576,8 @@
                 # call xslt transform on Inkscape's SVG to generate XHTML
                 try: 
                     self.ProgressStart("xslt", "XSLT transform")
-                    result = transform.transform(svgdom)  # , profile_run=True)
+                    result = transform.transform(
+                        svgdom, instance_name=location_str)  # , profile_run=True)
                     self.ProgressEnd("xslt")
                 except XSLTApplyError as e:
                     self.FatalError("SVGHMI " + svghmi_options["name"] + ": " + e.message)
@@ -826,8 +831,11 @@
                 self.GetCTRoot().logger.write_error(
                     _("Font file does not exist: %s\n") % fontfile)
         
+    def CTNGlobalInstances(self):
+        location_str = "_".join(map(str, self.GetCurrentLocation()))
+        return [("CURRENT_PAGE_"+location_str, "HMI_STRING", "")]
+
     ## In case one day we support more than one heartbeat
-    # def CTNGlobalInstances(self):
     #     view_name = self.BaseParams.getName()
     #     return [(view_name + "_HEARTBEAT", "HMI_INT", "")]
 
--- a/svghmi/widget_jsontable.ysl2	Tue Nov 30 18:43:10 2021 +0100
+++ b/svghmi/widget_jsontable.ysl2	Sun Jan 16 17:00:58 2022 +0100
@@ -6,7 +6,7 @@
     Send given variables as POST to http URL argument, spread returned JSON in
     SVG sub-elements of "data" labeled element.
     
-    Documentation to be written. see svbghmi exemple.
+    Documentation to be written. see svghmi exemple.
     ||
 
     shortdesc > Http POST variables, spread JSON back
@@ -151,15 +151,15 @@
 
 template "svg:use", mode="json_table_elt_render" {
     param "expressions";
-    // cloned element must be part of a HMI:List
+    // cloned element must be part of a HMI:List or a HMI:List
     const "targetid", "substring-after(@xlink:href,'#')";
     const "from_list", "$hmi_lists[(@id | */@id) = $targetid]";
 
     choose {
         when "count($from_list) > 0" {
-            |         id("«@id»").setAttribute("xlink:href",
+            |         id("«@id»").href.baseVal =
             // obtain new target id from HMI:List widget
-            |             "#"+hmi_widgets["«$from_list/@id»"].items[«$expressions/expression[1]/@content»]);
+            |             "#"+hmi_widgets["«$from_list/@id»"].items[«$expressions/expression[1]/@content»];
         }
         otherwise
             warning > Clones (svg:use) in JsonTable Widget must point to a valid HMI:List widget or item. Reference "«@xlink:href»" is not valid and will not be updated.
--- a/svghmi/widget_list.ysl2	Tue Nov 30 18:43:10 2021 +0100
+++ b/svghmi/widget_list.ysl2	Sun Jan 16 17:00:58 2022 +0100
@@ -22,7 +22,7 @@
 widget_defs("List") {
     |     items: {
     foreach "$hmi_element/*[@inkscape:label]" {
-    |         «@inkscape:label»: "«@id»",
+    |         "«@inkscape:label»": "«@id»",
     }
     |     },
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/svghmi/widget_listswitch.ysl2	Sun Jan 16 17:00:58 2022 +0100
@@ -0,0 +1,38 @@
+// widget_switch.ysl2
+
+widget_desc("ListSwitch") {
+    longdesc
+    ||
+    ListSwitch widget displays one item of an HMI:List depending on value of
+    given variable. Main element of the widget must be a clone of the list or
+    of an item of that list.  
+
+    Given variable's current value is compared to list items
+    label. For exemple if given variable type
+    is HMI_INT and value is 1, then item with label '1' will be displayed.
+    If matching variable of type HMI_STRING, then no quotes are needed. 
+    For exemple, 'hello' match HMI_STRING 'hello'.
+    ||
+
+    shortdesc > Displays item of an HMI:List whose label matches value.
+
+    path name="value" accepts="HMI_INT,HMI_STRING" > value to compare to labels
+    
+}
+
+widget_class("ListSwitch"){
+    ||
+        frequency = 5;
+    ||
+}
+
+widget_defs("ListSwitch") {
+    // cloned element must be part of a HMI:List or a HMI:List
+    const "targetid", "substring-after($hmi_element/@xlink:href,'#')";
+    const "from_list", "$hmi_lists[(@id | */@id) = $targetid]";
+    ||
+        dispatch: function(value) {
+            this.element.href.baseVal = "#"+hmi_widgets["«$from_list/@id»"].items[value];
+        },
+    ||
+}
--- a/svghmi/widget_switch.ysl2	Tue Nov 30 18:43:10 2021 +0100
+++ b/svghmi/widget_switch.ysl2	Sun Jan 16 17:00:58 2022 +0100
@@ -11,7 +11,7 @@
     '"hello"' or '"hello"#another comment' match HMI_STRING 'hello'.
     ||
 
-    shortdesc > Show elements whose label match value.
+    shortdesc > Show elements whose label matches value.
 
     // TODO: add optional format/precision argument to support floating points
     // TODO: support (in)equations and ranges
--- a/targets/Xenomai/plc_Xenomai_main.c	Tue Nov 30 18:43:10 2021 +0100
+++ b/targets/Xenomai/plc_Xenomai_main.c	Sun Jan 16 17:00:58 2022 +0100
@@ -384,6 +384,7 @@
 
 void resumeDebug(void)
 {
+    __DEBUG = 1;
     AtomicCompareExchange( &debug_state, DEBUG_BUSY, DEBUG_FREE);
 }
 
--- a/targets/plc_debug.c	Tue Nov 30 18:43:10 2021 +0100
+++ b/targets/plc_debug.c	Sun Jan 16 17:00:58 2022 +0100
@@ -25,22 +25,59 @@
 #include <string.h>
 #include <stdio.h>
 
+typedef unsigned int dbgvardsc_index_t;
+typedef unsigned short trace_buf_offset_t;
+
+#define BUFFER_EMPTY 0
+#define BUFFER_FULL 1
+
 #ifndef TARGET_ONLINE_DEBUG_DISABLE
-#define BUFFER_SIZE %(buffer_size)d
+
+#define TRACE_BUFFER_SIZE 4096
+#define TRACE_LIST_SIZE 1024
 
 /* Atomically accessed variable for buffer state */
-#define BUFFER_FREE 0
-#define BUFFER_BUSY 1
-static long buffer_state = BUFFER_FREE;
-
-/* The buffer itself */
-char debug_buffer[BUFFER_SIZE];
-
-/* Buffer's cursor*/
-static char* buffer_cursor = debug_buffer;
+static long trace_buffer_state = BUFFER_EMPTY;
+
+typedef struct trace_item_s {
+    dbgvardsc_index_t dbgvardsc_index;
+} trace_item_t;
+
+trace_item_t trace_list[TRACE_LIST_SIZE];
+char trace_buffer[TRACE_BUFFER_SIZE];
+
+/* Trace's cursor*/
+static trace_item_t *trace_list_collect_cursor = trace_list;
+static trace_item_t *trace_list_addvar_cursor = trace_list;
+static const trace_item_t *trace_list_end = 
+    &trace_list[TRACE_LIST_SIZE-1];
+static char *trace_buffer_cursor = trace_buffer;
+static const char *trace_buffer_end = trace_buffer + TRACE_BUFFER_SIZE;
+
+
+
+#define FORCE_BUFFER_SIZE 1024
+#define FORCE_LIST_SIZE 256
+
+typedef struct force_item_s {
+    dbgvardsc_index_t dbgvardsc_index;
+    void *value_pointer_backup;
+} force_item_t;
+
+force_item_t force_list[FORCE_LIST_SIZE];
+char force_buffer[FORCE_BUFFER_SIZE];
+
+/* Force's cursor*/
+static force_item_t *force_list_apply_cursor = force_list;
+static force_item_t *force_list_addvar_cursor = force_list;
+static const force_item_t *force_list_end = 
+    &force_list[FORCE_LIST_SIZE-1];
+static char *force_buffer_cursor = force_buffer;
+static const char *force_buffer_end = force_buffer + FORCE_BUFFER_SIZE;
+
+
 #endif
 
-static unsigned int retain_offset = 0;
 /***
  * Declare programs 
  **/
@@ -56,10 +93,16 @@
     __IEC_types_enum type;
 } dbgvardsc_t;
 
-static dbgvardsc_t dbgvardsc[] = {
+static const dbgvardsc_t dbgvardsc[] = {
 %(variable_decl_array)s
 };
 
+static const dbgvardsc_index_t retain_list[] = {
+%(retain_vardsc_index_array)s
+};
+static unsigned int retain_list_collect_cursor = 0;
+static const unsigned int retain_list_size = sizeof(retain_list)/sizeof(dbgvardsc_index_t);
+
 typedef void(*__for_each_variable_do_fp)(dbgvardsc_t*);
 void __for_each_variable_do(__for_each_variable_do_fp fp)
 {
@@ -77,23 +120,6 @@
 
 void Remind(unsigned int offset, unsigned int count, void * p);
 
-void RemindIterator(dbgvardsc_t *dsc)
-{
-    void *real_value_p = NULL;
-    char flags = 0;
-    UnpackVar(dsc, &real_value_p, &flags);
-
-    if(flags & __IEC_RETAIN_FLAG){
-        USINT size = __get_type_enum_size(dsc->type);
-        /* compute next cursor positon*/
-        unsigned int next_retain_offset = retain_offset + size;
-        /* if buffer not full */
-        Remind(retain_offset, size, real_value_p);
-        /* increment cursor according size*/
-        retain_offset = next_retain_offset;
-    }
-}
-
 extern int CheckRetainBuffer(void);
 extern void InitRetain(void);
 
@@ -101,20 +127,46 @@
 {
     /* init local static vars */
 #ifndef TARGET_ONLINE_DEBUG_DISABLE
-    buffer_cursor = debug_buffer;
-    buffer_state = BUFFER_FREE;
+    trace_buffer_cursor = trace_buffer;
+    trace_list_addvar_cursor = trace_list;
+    trace_list_collect_cursor = trace_list;
+    trace_buffer_state = BUFFER_EMPTY;
+
+    force_buffer_cursor = force_buffer;
+    force_list_addvar_cursor = force_list;
+    force_list_apply_cursor = force_list;
 #endif
 
-    retain_offset = 0;
     InitRetain();
     /* Iterate over all variables to fill debug buffer */
     if(CheckRetainBuffer()){
-        __for_each_variable_do(RemindIterator);
+        static unsigned int retain_offset = 0;
+        retain_list_collect_cursor = 0;
+
+        /* iterate over retain list */
+        while(retain_list_collect_cursor < retain_list_size){
+            void *value_p = NULL;
+            size_t size;
+            char* next_cursor;
+
+            dbgvardsc_t *dsc = &dbgvardsc[
+                retain_list[retain_list_collect_cursor]];
+
+            UnpackVar(dsc, &value_p, NULL, &size);
+
+            printf("Reminding %%d %%ld \n", retain_list_collect_cursor, size);
+
+            /* if buffer not full */
+            Remind(retain_offset, size, value_p);
+            /* increment cursor according size*/
+            retain_offset += size;
+
+            retain_list_collect_cursor++;
+        }
     }else{
         char mstr[] = "RETAIN memory invalid - defaults used";
         LogMessage(LOG_WARNING, mstr, sizeof(mstr));
     }
-    retain_offset = 0;
 }
 
 extern void InitiateDebugTransfer(void);
@@ -125,7 +177,7 @@
 void __cleanup_debug(void)
 {
 #ifndef TARGET_ONLINE_DEBUG_DISABLE
-    buffer_cursor = debug_buffer;
+    trace_buffer_cursor = trace_buffer;
     InitiateDebugTransfer();
 #endif    
 
@@ -136,84 +188,31 @@
 {
 }
 
-
 void Retain(unsigned int offset, unsigned int count, void * p);
 
-static inline void BufferIterator(dbgvardsc_t *dsc, int do_debug)
-{
-    void *real_value_p = NULL;
-    void *visible_value_p = NULL;
-    char flags = 0;
-
-    visible_value_p = UnpackVar(dsc, &real_value_p, &flags);
-
-    if(flags & ( __IEC_DEBUG_FLAG | __IEC_RETAIN_FLAG)){
-        USINT size = __get_type_enum_size(dsc->type);
-
-#ifndef TARGET_ONLINE_DEBUG_DISABLE
-        if(flags & __IEC_DEBUG_FLAG){
-            /* copy visible variable to buffer */;
-            if(do_debug){
-                /* compute next cursor positon.
-                   No need to check overflow, as BUFFER_SIZE
-                   is computed large enough */
-                if(__Is_a_string(dsc)){
-                    /* optimization for strings */
-                    size = ((STRING*)visible_value_p)->len + 1;
-                }
-                char* next_cursor = buffer_cursor + size;
-                /* copy data to the buffer */
-                memcpy(buffer_cursor, visible_value_p, size);
-                /* increment cursor according size*/
-                buffer_cursor = next_cursor;
-            }
-            /* re-force real value of outputs (M and Q)*/
-            if((flags & __IEC_FORCE_FLAG) && (flags & __IEC_OUTPUT_FLAG)){
-                memcpy(real_value_p, visible_value_p, size);
-            }
-        }
-#endif
-
-        if(flags & __IEC_RETAIN_FLAG){
-            /* compute next cursor positon*/
-            unsigned int next_retain_offset = retain_offset + size;
-            /* if buffer not full */
-            Retain(retain_offset, size, real_value_p);
-            /* increment cursor according size*/
-            retain_offset = next_retain_offset;
-        }
-    }
-}
-
-void DebugIterator(dbgvardsc_t *dsc){
-    BufferIterator(dsc, 1);
-}
-
-void RetainIterator(dbgvardsc_t *dsc){
-    BufferIterator(dsc, 0);
-}
-
-
-unsigned int retain_size = 0;
-
-/* GetRetainSizeIterator */
-void GetRetainSizeIterator(dbgvardsc_t *dsc)
-{
-    void *real_value_p = NULL;
-    char flags = 0;
-    UnpackVar(dsc, &real_value_p, &flags);
-
-    if(flags & __IEC_RETAIN_FLAG){
-        USINT size = __get_type_enum_size(dsc->type);
-        /* Calc retain buffer size */
-        retain_size += size;
-    }
-}
-
 /* Return size of all retain variables */
 unsigned int GetRetainSize(void)
 {
-    __for_each_variable_do(GetRetainSizeIterator);
+    unsigned int retain_size = 0;
+    retain_list_collect_cursor = 0;
+
+    /* iterate over retain list */
+    while(retain_list_collect_cursor < retain_list_size){
+        void *value_p = NULL;
+        size_t size;
+        char* next_cursor;
+
+        dbgvardsc_t *dsc = &dbgvardsc[
+            retain_list[retain_list_collect_cursor]];
+
+        UnpackVar(dsc, &value_p, NULL, &size);
+
+        retain_size += size;
+        retain_list_collect_cursor++;
+    }
+
+    printf("Retain size %%d \n", retain_size);
+            
     return retain_size;
 }
 
@@ -226,9 +225,26 @@
 extern void ValidateRetainBuffer(void);
 extern void InValidateRetainBuffer(void);
 
+#define __ReForceOutput_case_p(TYPENAME)                                                            \
+        case TYPENAME##_P_ENUM :                                                                    \
+        case TYPENAME##_O_ENUM :                                                                    \
+            {                                                                                       \
+                char *next_cursor = force_buffer_cursor + sizeof(TYPENAME);                         \
+                if(next_cursor <= force_buffer_end ){                                               \
+                    /* outputs real value must be systematically forced */                          \
+                    if(vartype == TYPENAME##_O_ENUM)                                                \
+                        /* overwrite value pointed by backup */                                     \
+                        *((TYPENAME *)force_list_apply_cursor->value_pointer_backup) =  \
+                            *((TYPENAME *)force_buffer_cursor);                                     \
+                    /* inc force_buffer cursor */                                                   \
+                    force_buffer_cursor = next_cursor;                                              \
+                }else{                                                                              \
+                    stop = 1;                                                                       \
+                }                                                                                   \
+            }                                                                                       \
+            break;
 void __publish_debug(void)
 {
-    retain_offset = 0;
     InValidateRetainBuffer();
     
 #ifndef TARGET_ONLINE_DEBUG_DISABLE 
@@ -236,116 +252,248 @@
     if(TryEnterDebugSection()){
         /* Lock buffer */
         long latest_state = AtomicCompareExchange(
-            &buffer_state,
-            BUFFER_FREE,
-            BUFFER_BUSY);
+            &trace_buffer_state,
+            BUFFER_EMPTY,
+            BUFFER_FULL);
             
         /* If buffer was free */
-        if(latest_state == BUFFER_FREE)
+        if(latest_state == BUFFER_EMPTY)
         {
+            int stop = 0;
+            /* Reset force list cursor */
+            force_list_apply_cursor = force_list;
+
+            /* iterate over force list */
+            while(!stop && force_list_apply_cursor < force_list_addvar_cursor){
+                dbgvardsc_t *dsc = &dbgvardsc[
+                    force_list_apply_cursor->dbgvardsc_index];
+                void *varp = dsc->ptr;
+                __IEC_types_enum vartype = dsc->type;
+                switch(vartype){
+                    __ANY(__ReForceOutput_case_p)
+                default:
+                    break;
+                }
+                force_list_apply_cursor++;                                                      \
+            }
+
             /* Reset buffer cursor */
-            buffer_cursor = debug_buffer;
-            /* Iterate over all variables to fill debug buffer */
-            __for_each_variable_do(DebugIterator);
+            trace_buffer_cursor = trace_buffer;
+            /* Reset trace list cursor */
+            trace_list_collect_cursor = trace_list;
+
+            /* iterate over trace list */
+            while(trace_list_collect_cursor < trace_list_addvar_cursor){
+                void *value_p = NULL;
+                size_t size;
+                char* next_cursor;
+
+                dbgvardsc_t *dsc = &dbgvardsc[
+                    trace_list_collect_cursor->dbgvardsc_index];
+
+                UnpackVar(dsc, &value_p, NULL, &size);
+
+                /* copy visible variable to buffer */;
+                if(__Is_a_string(dsc)){
+                    /* optimization for strings */
+                    /* assume NULL terminated strings */
+                    size = ((STRING*)value_p)->len + 1;
+                }
+
+                /* compute next cursor positon.*/
+                next_cursor = trace_buffer_cursor + size;
+                /* check for buffer overflow */
+                if(next_cursor < trace_buffer_end)
+                    /* copy data to the buffer */
+                    memcpy(trace_buffer_cursor, value_p, size);
+                else
+                    /* stop looping in case of overflow */
+                    break;
+                /* increment cursor according size*/
+                trace_buffer_cursor = next_cursor;
+                trace_list_collect_cursor++;
+            }
             
             /* Leave debug section,
              * Trigger asynchronous transmission 
              * (returns immediately) */
             InitiateDebugTransfer(); /* size */
-        }else{
-            /* when not debugging, do only retain */
-            __for_each_variable_do(RetainIterator);
         }
         LeaveDebugSection();
-    }else
+    }
 #endif
-    {
-        /* when not debugging, do only retain */
-        __for_each_variable_do(RetainIterator);
+    static unsigned int retain_offset = 0;
+    /* when not debugging, do only retain */
+    retain_list_collect_cursor = 0;
+
+    /* iterate over retain list */
+    while(retain_list_collect_cursor < retain_list_size){
+        void *value_p = NULL;
+        size_t size;
+        char* next_cursor;
+
+        dbgvardsc_t *dsc = &dbgvardsc[
+            retain_list[retain_list_collect_cursor]];
+
+        UnpackVar(dsc, &value_p, NULL, &size);
+
+        printf("Retaining %%d %%ld \n", retain_list_collect_cursor, size);
+
+        /* if buffer not full */
+        Retain(retain_offset, size, value_p);
+        /* increment cursor according size*/
+        retain_offset += size;
+
+        retain_list_collect_cursor++;
     }
     ValidateRetainBuffer();
 }
 
 #ifndef TARGET_ONLINE_DEBUG_DISABLE
-#define __RegisterDebugVariable_case_t(TYPENAME) \
-        case TYPENAME##_ENUM :\
-            ((__IEC_##TYPENAME##_t *)varp)->flags |= flags;\
-            if(force)\
-             ((__IEC_##TYPENAME##_t *)varp)->value = *((TYPENAME *)force);\
+
+#define TRACE_LIST_OVERFLOW    1
+#define FORCE_LIST_OVERFLOW    2
+#define FORCE_BUFFER_OVERFLOW  3
+
+#define __ForceVariable_case_t(TYPENAME)                                                \
+        case TYPENAME##_ENUM :                                                          \
+            /* add to force_list*/                                                      \
+            force_list_addvar_cursor->dbgvardsc_index = idx;                            \
+            ((__IEC_##TYPENAME##_t *)varp)->flags |= __IEC_FORCE_FLAG;                  \
+            ((__IEC_##TYPENAME##_t *)varp)->value = *((TYPENAME *)force);               \
             break;
-#define __RegisterDebugVariable_case_p(TYPENAME)\
-        case TYPENAME##_P_ENUM :\
-            ((__IEC_##TYPENAME##_p *)varp)->flags |= flags;\
-            if(force)\
-             ((__IEC_##TYPENAME##_p *)varp)->fvalue = *((TYPENAME *)force);\
-            break;\
-        case TYPENAME##_O_ENUM :\
-            ((__IEC_##TYPENAME##_p *)varp)->flags |= flags;\
-            if(force){\
-             ((__IEC_##TYPENAME##_p *)varp)->fvalue = *((TYPENAME *)force);\
-             *(((__IEC_##TYPENAME##_p *)varp)->value) = *((TYPENAME *)force);\
-            }\
+#define __ForceVariable_case_p(TYPENAME)                                                \
+        case TYPENAME##_P_ENUM :                                                        \
+        case TYPENAME##_O_ENUM :                                                        \
+            {                                                                           \
+                char *next_cursor = force_buffer_cursor + sizeof(TYPENAME);             \
+                if(next_cursor <= force_buffer_end ){                                   \
+                    /* add to force_list*/                                              \
+                    force_list_addvar_cursor->dbgvardsc_index = idx;                    \
+                    /* save pointer to backup */                                        \
+                    force_list_addvar_cursor->value_pointer_backup =                    \
+                        ((__IEC_##TYPENAME##_p *)varp)->value;                          \
+                    /* store forced value in force_buffer */                            \
+                    *((TYPENAME *)force_buffer_cursor) = *((TYPENAME *)force);          \
+                    /* replace pointer with pointer to force_buffer */                  \
+                    ((__IEC_##TYPENAME##_p *)varp)->value =                             \
+                        (TYPENAME *)force_buffer_cursor;                                \
+                    /* mark variable as forced */                                       \
+                    ((__IEC_##TYPENAME##_p *)varp)->flags |= __IEC_FORCE_FLAG;          \
+                    /* inc force_buffer cursor */                                       \
+                    force_buffer_cursor = next_cursor;                                  \
+                    /* outputs real value must be systematically forced */              \
+                    if(vartype == TYPENAME##_O_ENUM)                                    \
+                        *(((__IEC_##TYPENAME##_p *)varp)->value) = *((TYPENAME *)force);\
+                } else {                                                                \
+                    error_code = FORCE_BUFFER_OVERFLOW;                                 \
+                    goto error_cleanup;                                                 \
+                }                                                                       \
+            }                                                                           \
             break;
-void RegisterDebugVariable(unsigned int idx, void* force)
-{
-    if(idx  < sizeof(dbgvardsc)/sizeof(dbgvardsc_t)){
-        unsigned char flags = force ?
-            __IEC_DEBUG_FLAG | __IEC_FORCE_FLAG :
-            __IEC_DEBUG_FLAG;
-        dbgvardsc_t *dsc = &dbgvardsc[idx];
+
+
+void ResetDebugVariables(void);
+
+int RegisterDebugVariable(dbgvardsc_index_t idx, void* force)
+{
+    int error_code = 0;
+    if(idx < sizeof(dbgvardsc)/sizeof(dbgvardsc_t)){
+        /* add to trace_list, inc trace_list_addvar_cursor*/
+        if(trace_list_addvar_cursor <= trace_list_end){
+            trace_list_addvar_cursor->dbgvardsc_index = idx;
+            trace_list_addvar_cursor++;
+        } else {
+            error_code = TRACE_LIST_OVERFLOW;
+            goto error_cleanup;
+        }
+        if(force){
+            if(force_list_addvar_cursor <= force_list_end){
+                dbgvardsc_t *dsc = &dbgvardsc[idx];
+                void *varp = dsc->ptr;
+                __IEC_types_enum vartype = dsc->type;
+
+                switch(vartype){
+                    __ANY(__ForceVariable_case_t)
+                    __ANY(__ForceVariable_case_p)
+                default:
+                    break;
+                }
+                /* inc force_list cursor */
+                force_list_addvar_cursor++;
+            } else {
+                error_code = FORCE_LIST_OVERFLOW;
+                goto error_cleanup;
+            }
+        }
+    }
+    return 0;
+
+error_cleanup:
+    ResetDebugVariables();
+    trace_buffer_state = BUFFER_EMPTY;
+    return error_code;
+    
+}
+
+#define ResetForcedVariable_case_t(TYPENAME)                                            \
+        case TYPENAME##_ENUM :                                                          \
+            ((__IEC_##TYPENAME##_t *)varp)->flags &= ~__IEC_FORCE_FLAG;                 \
+            /* for local variable we don't restore original value */                    \
+            /* that can be added if needed, but it was like that since ever */          \
+            break;
+
+#define ResetForcedVariable_case_p(TYPENAME)                                            \
+        case TYPENAME##_P_ENUM :                                                        \
+        case TYPENAME##_O_ENUM :                                                        \
+            ((__IEC_##TYPENAME##_p *)varp)->flags &= ~__IEC_FORCE_FLAG;                 \
+            /* restore backup to pointer */                                             \
+            ((__IEC_##TYPENAME##_p *)varp)->value =                                     \
+                force_list_apply_cursor->value_pointer_backup;                          \
+            break;
+
+void ResetDebugVariables(void)
+{
+    /* Reset trace list */
+    trace_list_addvar_cursor = trace_list;
+
+    force_list_apply_cursor = force_list;
+    /* Restore forced variables */
+    while(force_list_apply_cursor < force_list_addvar_cursor){
+        dbgvardsc_t *dsc = &dbgvardsc[
+            force_list_apply_cursor->dbgvardsc_index];
         void *varp = dsc->ptr;
         switch(dsc->type){
-            __ANY(__RegisterDebugVariable_case_t)
-            __ANY(__RegisterDebugVariable_case_p)
+            __ANY(ResetForcedVariable_case_t)
+            __ANY(ResetForcedVariable_case_p)
         default:
             break;
         }
-    }
-}
-
-#define __ResetDebugVariablesIterator_case_t(TYPENAME) \
-        case TYPENAME##_ENUM :\
-            ((__IEC_##TYPENAME##_t *)varp)->flags &= ~(__IEC_DEBUG_FLAG|__IEC_FORCE_FLAG);\
-            break;
-
-#define __ResetDebugVariablesIterator_case_p(TYPENAME)\
-        case TYPENAME##_P_ENUM :\
-        case TYPENAME##_O_ENUM :\
-            ((__IEC_##TYPENAME##_p *)varp)->flags &= ~(__IEC_DEBUG_FLAG|__IEC_FORCE_FLAG);\
-            break;
-
-void ResetDebugVariablesIterator(dbgvardsc_t *dsc)
-{
-    /* force debug flag to 0*/
-    void *varp = dsc->ptr;
-    switch(dsc->type){
-        __ANY(__ResetDebugVariablesIterator_case_t)
-        __ANY(__ResetDebugVariablesIterator_case_p)
-    default:
-        break;
-    }
-}
-
-void ResetDebugVariables(void)
-{
-    __for_each_variable_do(ResetDebugVariablesIterator);
+        /* inc force_list cursor */
+        force_list_apply_cursor++;
+    } /* else TODO: warn user about failure to force */ 
+
+    /* Reset force list */
+    force_list_addvar_cursor = force_list;
+    /* Reset force buffer */
+    force_buffer_cursor = force_buffer;
 }
 
 void FreeDebugData(void)
 {
     /* atomically mark buffer as free */
     AtomicCompareExchange(
-        &buffer_state,
-        BUFFER_BUSY,
-        BUFFER_FREE);
+        &trace_buffer_state,
+        BUFFER_FULL,
+        BUFFER_EMPTY);
 }
 int WaitDebugData(unsigned long *tick);
 /* Wait until debug data ready and return pointer to it */
 int GetDebugData(unsigned long *tick, unsigned long *size, void **buffer){
     int wait_error = WaitDebugData(tick);
     if(!wait_error){
-        *size = buffer_cursor - debug_buffer;
-        *buffer = debug_buffer;
+        *size = trace_buffer_cursor - trace_buffer;
+        *buffer = trace_buffer;
     }
     return wait_error;
 }
--- a/targets/var_access.c	Tue Nov 30 18:43:10 2021 +0100
+++ b/targets/var_access.c	Sun Jan 16 17:00:58 2022 +0100
@@ -1,37 +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;\
+#define __Unpack_case_t(TYPENAME)                                           \
+        case TYPENAME##_ENUM :                                              \
+            if(flags) *flags = ((__IEC_##TYPENAME##_t *)varp)->flags;       \
+            if(value_p) *value_p = &((__IEC_##TYPENAME##_t *)varp)->value;  \
+		    if(size) *size = sizeof(TYPENAME);                              \
             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;\
+#define __Unpack_case_p(TYPENAME)                                           \
+        case TYPENAME##_O_ENUM :                                            \
+        case TYPENAME##_P_ENUM :                                            \
+            if(flags) *flags = ((__IEC_##TYPENAME##_p *)varp)->flags;       \
+            if(value_p) *value_p = ((__IEC_##TYPENAME##_p *)varp)->value;   \
+		    if(size) *size = sizeof(TYPENAME);                              \
             break;
 
 #define __Is_a_string(dsc) (dsc->type == STRING_ENUM)   ||\
                            (dsc->type == STRING_P_ENUM) ||\
                            (dsc->type == STRING_O_ENUM)
 
-static void* UnpackVar(__Unpack_desc_type *dsc, void **real_value_p, char *flags)
+static int UnpackVar(__Unpack_desc_type *dsc, void **value_p, char *flags, size_t *size)
 {
     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;
+        return 0; /* should never happen */
     }
-    if (*flags & __IEC_FORCE_FLAG)
-        return forced_value_p;
-    return *real_value_p;
+    return 1;
 }
 
--- a/tests/svghmi/plc.xml	Tue Nov 30 18:43:10 2021 +0100
+++ b/tests/svghmi/plc.xml	Sun Jan 16 17:00:58 2022 +0100
@@ -1,7 +1,7 @@
 <?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="2021-10-03T20:43:39">
+  <contentHeader name="Unnamed" modificationDateTime="2021-11-08T13:54:01">
     <coordinateInfo>
       <fbd>
         <scaling x="5" y="5"/>
@@ -71,6 +71,25 @@
               </type>
             </variable>
           </localVars>
+          <externalVars>
+            <variable name="CURRENT_PAGE_0">
+              <type>
+                <derived name="HMI_STRING"/>
+              </type>
+            </variable>
+          </externalVars>
+          <localVars>
+            <variable name="PAGESWITCH">
+              <type>
+                <BOOL/>
+              </type>
+            </variable>
+            <variable name="R_TRIG0">
+              <type>
+                <derived name="R_TRIG"/>
+              </type>
+            </variable>
+          </localVars>
         </interface>
         <body>
           <FBD>
@@ -287,6 +306,100 @@
               </connectionPointOut>
               <expression>0</expression>
             </inVariable>
+            <inOutVariable localId="12" executionOrderId="0" height="25" width="125" negatedOut="false" negatedIn="false">
+              <position x="410" y="205"/>
+              <connectionPointIn>
+                <relPosition x="0" y="10"/>
+                <connection refLocalId="13" formalParameter="OUT">
+                  <position x="410" y="215"/>
+                  <position x="385" y="215"/>
+                </connection>
+              </connectionPointIn>
+              <connectionPointOut>
+                <relPosition x="125" y="10"/>
+              </connectionPointOut>
+              <expression>CURRENT_PAGE_0</expression>
+            </inOutVariable>
+            <block localId="13" typeName="SEL" executionOrderId="0" height="80" width="65">
+              <position x="320" y="185"/>
+              <inputVariables>
+                <variable formalParameter="G">
+                  <connectionPointIn>
+                    <relPosition x="0" y="30"/>
+                    <connection refLocalId="17" formalParameter="Q">
+                      <position x="320" y="215"/>
+                      <position x="280" y="215"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN0">
+                  <connectionPointIn>
+                    <relPosition x="0" y="50"/>
+                    <connection refLocalId="12">
+                      <position x="320" y="235"/>
+                      <position x="60" y="235"/>
+                      <position x="60" y="155"/>
+                      <position x="550" y="155"/>
+                      <position x="550" y="215"/>
+                      <position x="535" y="215"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN1">
+                  <connectionPointIn>
+                    <relPosition x="0" y="70"/>
+                    <connection refLocalId="16">
+                      <position x="320" y="255"/>
+                      <position x="290" y="255"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="65" y="30"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inVariable localId="15" executionOrderId="0" height="25" width="90" negated="false">
+              <position x="100" y="205"/>
+              <connectionPointOut>
+                <relPosition x="90" y="10"/>
+              </connectionPointOut>
+              <expression>PAGESWITCH</expression>
+            </inVariable>
+            <inVariable localId="16" executionOrderId="0" height="25" width="220" negated="false">
+              <position x="70" y="245"/>
+              <connectionPointOut>
+                <relPosition x="220" y="10"/>
+              </connectionPointOut>
+              <expression>'!RelativePageTest@/PUMP2'</expression>
+            </inVariable>
+            <block localId="17" typeName="R_TRIG" instanceName="R_TRIG0" executionOrderId="0" height="40" width="60">
+              <position x="220" y="185"/>
+              <inputVariables>
+                <variable formalParameter="CLK">
+                  <connectionPointIn>
+                    <relPosition x="0" y="30"/>
+                    <connection refLocalId="15">
+                      <position x="220" y="215"/>
+                      <position x="190" y="215"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="Q">
+                  <connectionPointOut>
+                    <relPosition x="60" y="30"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
           </FBD>
         </body>
       </pou>
--- a/tests/svghmi/svghmi_0@svghmi/svghmi.svg	Tue Nov 30 18:43:10 2021 +0100
+++ b/tests/svghmi/svghmi_0@svghmi/svghmi.svg	Sun Jan 16 17:00:58 2022 +0100
@@ -128,14 +128,14 @@
      inkscape:current-layer="hmi0"
      showgrid="false"
      units="px"
-     inkscape:zoom="0.42177818"
-     inkscape:cx="543.13877"
-     inkscape:cy="-467.92793"
-     inkscape:window-width="2400"
-     inkscape:window-height="2096"
-     inkscape:window-x="3200"
+     inkscape:zoom="0.84355636"
+     inkscape:cx="-1054.5035"
+     inkscape:cy="482.64214"
+     inkscape:window-width="1600"
+     inkscape:window-height="836"
+     inkscape:window-x="0"
      inkscape:window-y="27"
-     inkscape:window-maximized="0"
+     inkscape:window-maximized="1"
      showguides="true"
      inkscape:guide-bbox="true"
      inkscape:snap-global="true"
@@ -7538,4 +7538,26 @@
        y="606.89435"
        style="text-align:center;text-anchor:middle;fill:#ff6600;stroke-width:0.99999994px"
        id="tspan1330">000</tspan></text>
+  <use
+     transform="translate(-2307.336,346.33773)"
+     x="0"
+     y="0"
+     xlink:href="#use1299"
+     inkscape:transform-center-x="0.14620371"
+     inkscape:transform-center-y="2.9995242"
+     id="use1176"
+     width="100%"
+     height="100%"
+     inkscape:label="HMI:ListSwitch@/ALARMSTATUS" />
+  <use
+     height="100%"
+     width="100%"
+     id="use1193"
+     inkscape:transform-center-y="2.9995242"
+     inkscape:transform-center-x="0.14620371"
+     xlink:href="#use1299"
+     y="0"
+     x="0"
+     transform="translate(-2307.336,1146.3377)"
+     inkscape:label="HMI:ListSwitch@/ALARMSTATUS" />
 </svg>