SVGHMI: Added init call to all widgets at startup to bind events. More features in Input widget : Edit and Change buttons. WIP HMI->PLC value update, incoherent data detected in C part on update. svghmi
authorEdouard Tisserant
Tue, 22 Oct 2019 17:06:31 +0200
branchsvghmi
changeset 2801 390acff12755
parent 2800 68cee1366b9c
child 2802 64e6f73b9859
SVGHMI: Added init call to all widgets at startup to bind events. More features in Input widget : Edit and Change buttons. WIP HMI->PLC value update, incoherent data detected in C part on update.
svghmi/gen_index_xhtml.xslt
svghmi/gen_index_xhtml.ysl2
svghmi/svghmi.js
tests/svghmi/beremiz.xml
tests/svghmi/svghmi_0@svghmi/svghmi.svg
--- a/svghmi/gen_index_xhtml.xslt	Sat Oct 19 01:23:30 2019 +0200
+++ b/svghmi/gen_index_xhtml.xslt	Tue Oct 22 17:06:31 2019 +0200
@@ -326,6 +326,10 @@
 </xsl:text>
     <xsl:text>
 </xsl:text>
+    <xsl:text>    // TODO : value cache
+</xsl:text>
+    <xsl:text>    
+</xsl:text>
     <xsl:text>    if(widgets.size &gt; 0) {
 </xsl:text>
     <xsl:text>        for(let widget of widgets){
@@ -364,6 +368,26 @@
 </xsl:text>
     <xsl:text>
 </xsl:text>
+    <xsl:text>function init_widgets() {
+</xsl:text>
+    <xsl:text>    Object.keys(hmi_widgets).forEach(function(id) {
+</xsl:text>
+    <xsl:text>        let widget = hmi_widgets[id];
+</xsl:text>
+    <xsl:text>        let init = widget.init;
+</xsl:text>
+    <xsl:text>        if(typeof(init) == "function"){
+</xsl:text>
+    <xsl:text>            return init.call(widget);
+</xsl:text>
+    <xsl:text>        }
+</xsl:text>
+    <xsl:text>    });
+</xsl:text>
+    <xsl:text>};
+</xsl:text>
+    <xsl:text>
+</xsl:text>
     <xsl:text>// Open WebSocket to relative "/ws" address
 </xsl:text>
     <xsl:text>var ws = new WebSocket(window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws'));
@@ -582,11 +606,11 @@
 </xsl:text>
     <xsl:text>
 </xsl:text>
-    <xsl:text>function update_value(index, value) {
+    <xsl:text>function send_hmi_value(index, value) {
 </xsl:text>
     <xsl:text>    iectype = hmitree_types[index];
 </xsl:text>
-    <xsl:text>    jstype = typedarray_types[iectypes];
+    <xsl:text>    jstype = typedarray_types[iectype];
 </xsl:text>
     <xsl:text>    send_blob([
 </xsl:text>
@@ -602,6 +626,22 @@
 </xsl:text>
     <xsl:text>
 </xsl:text>
+    <xsl:text>function change_hmi_value(index, opstr) {
+</xsl:text>
+    <xsl:text>    let op = opstr[0];
+</xsl:text>
+    <xsl:text>    if(op == "=")
+</xsl:text>
+    <xsl:text>        return send_hmi_value(index, Number(opstr.slice(1)));
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>    alert('Change '+opstr+" TODO !!! (index :"+index+")");
+</xsl:text>
+    <xsl:text>}
+</xsl:text>
+    <xsl:text>
+</xsl:text>
     <xsl:text>var current_page;
 </xsl:text>
     <xsl:text>
@@ -660,6 +700,8 @@
 </xsl:text>
     <xsl:text>ws.onopen = function (evt) {
 </xsl:text>
+    <xsl:text>    init_widgets();
+</xsl:text>
     <xsl:text>    send_reset();
 </xsl:text>
     <xsl:text>    // show main page
@@ -773,18 +815,73 @@
 </xsl:text>
       </xsl:when>
       <xsl:otherwise>
-        <xsl:message terminate="yes">Display widget as a group not implemented</xsl:message>
+        <xsl:message terminate="yes">
+          <xsl:text>Display widget as a group not implemented</xsl:text>
+        </xsl:message>
       </xsl:otherwise>
     </xsl:choose>
     <xsl:text>},
 </xsl:text>
   </xsl:template>
   <xsl:template mode="widget_defs" match="widget[@type='Meter']">
-    <xsl:text>    frequency: 10,
+    <xsl:text>frequency: 10,
 </xsl:text>
   </xsl:template>
   <xsl:template mode="widget_defs" match="widget[@type='Input']">
+    <xsl:param name="hmi_element"/>
+    <xsl:text>frequency: 5,
+</xsl:text>
+    <xsl:variable name="value_elt_id" select="$hmi_element//*[self::svg:text][@inkscape:label='value'][1]/@id"/>
+    <xsl:if test="not($value_elt_id)">
+      <xsl:message terminate="yes">
+        <xsl:text>Input widget must have a text element</xsl:text>
+      </xsl:message>
+    </xsl:if>
+    <xsl:text>value_elt: document.getElementById("</xsl:text>
+    <xsl:value-of select="$value_elt_id"/>
+    <xsl:text>"),
+</xsl:text>
+    <xsl:text>dispatch: function(value) {
+</xsl:text>
+    <xsl:text>    this.value_elt.textContent = String(value);
+</xsl:text>
+    <xsl:text>},
+</xsl:text>
+    <xsl:variable name="edit_elt_id" select="$hmi_element/*[@inkscape:label='edit'][1]/@id"/>
+    <xsl:text>init: function() {
+</xsl:text>
+    <xsl:if test="$edit_elt_id">
+      <xsl:text>    document.getElementById("</xsl:text>
+      <xsl:value-of select="$edit_elt_id"/>
+      <xsl:text>").addEventListener(
+</xsl:text>
+      <xsl:text>        "click", 
+</xsl:text>
+      <xsl:text>        evt =&gt; alert('XXX TODO : Edit value'));
+</xsl:text>
+    </xsl:if>
+    <xsl:for-each select="$hmi_element/*[regexp:test(@inkscape:label,'^[=+\-][0-9]+')]">
+      <xsl:text>    document.getElementById("</xsl:text>
+      <xsl:value-of select="@id"/>
+      <xsl:text>").addEventListener(
+</xsl:text>
+      <xsl:text>        "click", 
+</xsl:text>
+      <xsl:text>        evt =&gt; change_hmi_value(this.indexes[0], "</xsl:text>
+      <xsl:value-of select="@inkscape:label"/>
+      <xsl:text>"));
+</xsl:text>
+    </xsl:for-each>
+    <xsl:text>},
+</xsl:text>
+  </xsl:template>
+  <xsl:template mode="widget_defs" match="widget[@type='Button']"/>
+  <xsl:template mode="widget_defs" match="widget[@type='Toggle']">
     <xsl:text>    frequency: 5,
 </xsl:text>
   </xsl:template>
+  <xsl:template mode="widget_defs" match="widget[@type='Change']">
+    <xsl:text>    frequency: 5,
+</xsl:text>
+  </xsl:template>
 </xsl:stylesheet>
--- a/svghmi/gen_index_xhtml.ysl2	Sat Oct 19 01:23:30 2019 +0200
+++ b/svghmi/gen_index_xhtml.ysl2	Tue Oct 22 17:06:31 2019 +0200
@@ -317,16 +317,47 @@
         |   this.element.textContent = String(value);
             }
             otherwise {
-                error "Display widget as a group not implemented";
+                error > Display widget as a group not implemented
             }
         }
         | },
 
     }
     template "widget[@type='Meter']", mode="widget_defs" {
-        |     frequency: 10,
+        | frequency: 10,
     }
     template "widget[@type='Input']", mode="widget_defs" {
+        param "hmi_element";
+        | frequency: 5,
+        const "value_elt_id","$hmi_element//*[self::svg:text][@inkscape:label='value'][1]/@id";
+        if "not($value_elt_id)" error > Input widget must have a text element
+        | value_elt: document.getElementById("«$value_elt_id»"),
+        | dispatch: function(value) {
+        |     this.value_elt.textContent = String(value);
+        | },
+        const "edit_elt_id","$hmi_element/*[@inkscape:label='edit'][1]/@id";
+        | init: function() {
+        if "$edit_elt_id" {
+        |     document.getElementById("«$edit_elt_id»").addEventListener(
+        |         "click", 
+        |         evt => alert('XXX TODO : Edit value'));
+        }
+        foreach "$hmi_element/*[regexp:test(@inkscape:label,'^[=+\-][0-9]+')]" {
+        |     document.getElementById("«@id»").addEventListener(
+        |         "click", 
+        |         evt => change_hmi_value(this.indexes[0], "«@inkscape:label»"));
+        }
+        | },
+    }
+    template "widget[@type='Button']", mode="widget_defs" {
+    }
+    template "widget[@type='Toggle']", mode="widget_defs" {
+        |     frequency: 5,
+    }
+    template "widget[@type='Change']", mode="widget_defs" {
+        // HMI:Change:-10@/PUMP/VALUE 
+        // HMI:Change:+1@/PUMP/VALUE 
+        // HMI:Change:=42@/PUMP/VALUE 
         |     frequency: 5,
     }
     //    |     frequency: 10`apply ".", mode="refresh_frequency"`,
--- a/svghmi/svghmi.js	Sat Oct 19 01:23:30 2019 +0200
+++ b/svghmi/svghmi.js	Tue Oct 22 17:06:31 2019 +0200
@@ -3,6 +3,8 @@
 function dispatch_value(index, value) {
     let widgets = subscribers[index];
 
+    // TODO : value cache
+    
     if(widgets.size > 0) {
         for(let widget of widgets){
             let idxidx = widget.indexes.indexOf(index);
@@ -22,6 +24,16 @@
     }
 };
 
+function init_widgets() {
+    Object.keys(hmi_widgets).forEach(function(id) {
+        let widget = hmi_widgets[id];
+        let init = widget.init;
+        if(typeof(init) == "function"){
+            return init.call(widget);
+        }
+    });
+};
+
 // Open WebSocket to relative "/ws" address
 var ws = new WebSocket(window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws'));
 ws.binaryType = 'arraybuffer';
@@ -127,9 +139,9 @@
     send_blob(delta);
 };
 
-function update_value(index, value) {
+function send_hmi_value(index, value) {
     iectype = hmitree_types[index];
-    jstype = typedarray_types[iectypes];
+    jstype = typedarray_types[iectype];
     send_blob([
         new Uint8Array([0]),  /* setval = 0 */
         new jstype([value])
@@ -137,6 +149,14 @@
 
 };
 
+function change_hmi_value(index, opstr) {
+    let op = opstr[0];
+    if(op == "=")
+        return send_hmi_value(index, Number(opstr.slice(1)));
+
+    alert('Change '+opstr+" TODO !!! (index :"+index+")");
+}
+
 var current_page;
 
 function switch_page(page_name) {
@@ -166,6 +186,7 @@
 
 // Once connection established
 ws.onopen = function (evt) {
+    init_widgets();
     send_reset();
     // show main page
     switch_page(default_page);
--- a/tests/svghmi/beremiz.xml	Sat Oct 19 01:23:30 2019 +0200
+++ b/tests/svghmi/beremiz.xml	Tue Oct 22 17:06:31 2019 +0200
@@ -1,5 +1,5 @@
 <?xml version='1.0' encoding='utf-8'?>
-<BeremizRoot xmlns:xsd="http://www.w3.org/2001/XMLSchema" URI_location="LOCAL://">
+<BeremizRoot xmlns:xsd="http://www.w3.org/2001/XMLSchema" URI_location="PYRO://127.0.0.1:61284">
   <TargetType/>
   <Libraries Enable_SVGHMI_Library="true"/>
 </BeremizRoot>
--- a/tests/svghmi/svghmi_0@svghmi/svghmi.svg	Sat Oct 19 01:23:30 2019 +0200
+++ b/tests/svghmi/svghmi_0@svghmi/svghmi.svg	Tue Oct 22 17:06:31 2019 +0200
@@ -76,12 +76,12 @@
      inkscape:pageopacity="0"
      inkscape:pageshadow="2"
      inkscape:document-units="px"
-     inkscape:current-layer="hmi0"
+     inkscape:current-layer="g84"
      showgrid="false"
      units="px"
-     inkscape:zoom="0.421875"
-     inkscape:cx="486.11352"
-     inkscape:cy="131.25685"
+     inkscape:zoom="0.84375"
+     inkscape:cx="-12.406671"
+     inkscape:cy="380.60108"
      inkscape:window-width="1600"
      inkscape:window-height="886"
      inkscape:window-x="0"
@@ -174,18 +174,118 @@
            sodipodi:role="line">Settings</tspan></text>
     </g>
   </g>
-  <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="218.24219"
-     id="text5151"
-     inkscape:label="HMI:Input@/TARGETPRESSURE"><tspan
-       sodipodi:role="line"
-       id="tspan5149"
+  <g
+     id="g84"
+     inkscape:label="HMI:Input@/TARGETPRESSURE">
+    <text
+       inkscape:label="value"
+       id="text5151"
+       y="218.24219"
        x="136.32812"
-       y="218.24219"
-       style="stroke-width:1px">8888</tspan></text>
+       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"><tspan
+         style="stroke-width:1px"
+         y="218.24219"
+         x="136.32812"
+         id="tspan5149"
+         sodipodi:role="line">8888</tspan></text>
+    <path
+       transform="scale(1,-1)"
+       sodipodi:type="star"
+       style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       id="path89"
+       sodipodi:sides="3"
+       sodipodi:cx="580.74072"
+       sodipodi:cy="-236.2599"
+       sodipodi:r1="59.825443"
+       sodipodi:r2="29.912722"
+       sodipodi:arg1="0.52359878"
+       sodipodi:arg2="1.5707963"
+       inkscape:flatsided="true"
+       inkscape:rounded="0"
+       inkscape:randomized="0"
+       d="m 632.55108,-206.34718 -103.62071,0 51.81035,-89.73817 z"
+       inkscape:transform-center-y="14.956363"
+       inkscape:label="-100" />
+    <path
+       inkscape:label="-10"
+       inkscape:transform-center-y="7.4781812"
+       d="m 606.6459,-170.03172 -51.81035,0 25.90517,-44.86908 z"
+       inkscape:randomized="0"
+       inkscape:rounded="0"
+       inkscape:flatsided="true"
+       sodipodi:arg2="1.5707963"
+       sodipodi:arg1="0.52359878"
+       sodipodi:r2="14.956361"
+       sodipodi:r1="29.912722"
+       sodipodi:cy="-184.98808"
+       sodipodi:cx="580.74072"
+       sodipodi:sides="3"
+       id="path88"
+       style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       sodipodi:type="star"
+       transform="scale(1,-1)" />
+    <rect
+       style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ff00ff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       id="rect85"
+       width="407.7037"
+       height="128"
+       x="139.85185"
+       y="95.40741"
+       onclick=""
+       inkscape:label="edit" />
+    <path
+       inkscape:label="+100"
+       inkscape:transform-center-y="-14.956361"
+       d="m 632.55108,115.08534 -103.62071,0 51.81035,-89.738161 z"
+       inkscape:randomized="0"
+       inkscape:rounded="0"
+       inkscape:flatsided="true"
+       sodipodi:arg2="1.5707963"
+       sodipodi:arg1="0.52359878"
+       sodipodi:r2="29.912722"
+       sodipodi:r1="59.825443"
+       sodipodi:cy="85.172623"
+       sodipodi:cx="580.74072"
+       sodipodi:sides="3"
+       id="path87"
+       style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       sodipodi:type="star" />
+    <path
+       sodipodi:type="star"
+       style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       id="path86"
+       sodipodi:sides="3"
+       sodipodi:cx="580.74072"
+       sodipodi:cy="136.44444"
+       sodipodi:r1="29.912722"
+       sodipodi:r2="14.956361"
+       sodipodi:arg1="0.52359878"
+       sodipodi:arg2="1.5707963"
+       inkscape:flatsided="true"
+       inkscape:rounded="0"
+       inkscape:randomized="0"
+       d="m 606.6459,151.4008 -51.81035,0 25.90517,-44.86908 z"
+       inkscape:transform-center-y="-7.4781804"
+       inkscape:label="+10" />
+    <path
+       sodipodi:type="star"
+       style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       id="path91"
+       sodipodi:sides="4"
+       sodipodi:cx="80.740723"
+       sodipodi:cy="165.17262"
+       sodipodi:r1="57.015106"
+       sodipodi:r2="29.912722"
+       sodipodi:arg1="0.77793027"
+       sodipodi:arg2="1.5633284"
+       inkscape:flatsided="true"
+       inkscape:rounded="-0.65084865"
+       inkscape:randomized="0"
+       d="M 121.35644,205.1862 C 158.18649,167.80191 3.342862,168.95829 40.72715,205.78834 78.111437,242.61839 76.95506,87.774762 40.125008,125.15905 3.2949549,162.54334 158.13858,161.38696 120.7543,124.55691 83.370008,87.726855 84.526385,242.57048 121.35644,205.1862 Z"
+       inkscape:transform-center-y="-14.956361"
+       inkscape:label="=0" />
+  </g>
   <text
      inkscape:label="HMI:Display@/PUMP/PRESSURE"
      id="text823"