svghmi/gen_index_xhtml.xslt
branchsvghmi
changeset 2881 3bb49f93d48c
parent 2879 58e6a91dc37f
child 2883 8e3d130399b0
equal deleted inserted replaced
2880:9da4ac0c9add 2881:3bb49f93d48c
   525       <xsl:value-of select="@id"/>
   525       <xsl:value-of select="@id"/>
   526       <xsl:text>
   526       <xsl:text>
   527 </xsl:text>
   527 </xsl:text>
   528     </xsl:for-each>
   528     </xsl:for-each>
   529   </xsl:template>
   529   </xsl:template>
   530   <xsl:template match="/">
   530   <xsl:template mode="hmi_elements" match="svg:*">
   531     <xsl:comment>
   531     <xsl:variable name="widget" select="func:parselabel(@inkscape:label)/widget"/>
   532       <xsl:text>Made with SVGHMI. https://beremiz.org</xsl:text>
   532     <xsl:variable name="eltid" select="@id"/>
   533     </xsl:comment>
   533     <xsl:text>  "</xsl:text>
   534     <xsl:comment>
   534     <xsl:value-of select="@id"/>
   535       <xsl:text>
   535     <xsl:text>": {
   536 </xsl:text>
   536 </xsl:text>
   537       <xsl:text>debug_hmitree:
   537     <xsl:text>    type: "</xsl:text>
   538 </xsl:text>
   538     <xsl:value-of select="$widget/@type"/>
   539       <xsl:call-template name="debug_hmitree"/>
   539     <xsl:text>",
   540       <xsl:text>
   540 </xsl:text>
   541 </xsl:text>
   541     <xsl:text>    args: [
   542     </xsl:comment>
   542 </xsl:text>
   543     <xsl:comment>
   543     <xsl:for-each select="$widget/arg">
   544       <xsl:text>
   544       <xsl:text>        "</xsl:text>
   545 </xsl:text>
   545       <xsl:value-of select="@value"/>
   546       <xsl:text>debug_geometry:
       
   547 </xsl:text>
       
   548       <xsl:call-template name="debug_geometry"/>
       
   549       <xsl:text>
       
   550 </xsl:text>
       
   551     </xsl:comment>
       
   552     <xsl:comment>
       
   553       <xsl:text>
       
   554 </xsl:text>
       
   555       <xsl:text>debug_detachables:
       
   556 </xsl:text>
       
   557       <xsl:call-template name="debug_detachables"/>
       
   558       <xsl:text>
       
   559 </xsl:text>
       
   560     </xsl:comment>
       
   561     <xsl:comment>
       
   562       <xsl:text>
       
   563 </xsl:text>
       
   564       <xsl:text>debug_unlink:
       
   565 </xsl:text>
       
   566       <xsl:call-template name="debug_unlink"/>
       
   567       <xsl:text>
       
   568 </xsl:text>
       
   569     </xsl:comment>
       
   570     <html xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/1999/xhtml">
       
   571       <head/>
       
   572       <body style="margin:0;overflow:hidden;">
       
   573         <xsl:copy-of select="$result_svg"/>
       
   574         <script>
       
   575           <xsl:call-template name="scripts"/>
       
   576         </script>
       
   577       </body>
       
   578     </html>
       
   579   </xsl:template>
       
   580   <xsl:template name="scripts">
       
   581     <xsl:text>//(function(){
       
   582 </xsl:text>
       
   583     <xsl:text>
       
   584 </xsl:text>
       
   585     <xsl:text>id = idstr =&gt; document.getElementById(idstr);
       
   586 </xsl:text>
       
   587     <xsl:text>
       
   588 </xsl:text>
       
   589     <xsl:text>var hmi_hash = [</xsl:text>
       
   590     <xsl:value-of select="$hmitree/@hash"/>
       
   591     <xsl:text>]; 
       
   592 </xsl:text>
       
   593     <xsl:text>var hmi_widgets = {
       
   594 </xsl:text>
       
   595     <xsl:for-each select="$hmi_elements">
       
   596       <xsl:variable name="widget" select="func:parselabel(@inkscape:label)/widget"/>
       
   597       <xsl:variable name="eltid" select="@id"/>
       
   598       <xsl:text>  "</xsl:text>
       
   599       <xsl:value-of select="@id"/>
       
   600       <xsl:text>": {
       
   601 </xsl:text>
       
   602       <xsl:text>    type: "</xsl:text>
       
   603       <xsl:value-of select="$widget/@type"/>
       
   604       <xsl:text>",
       
   605 </xsl:text>
       
   606       <xsl:text>    args: [
       
   607 </xsl:text>
       
   608       <xsl:for-each select="$widget/arg">
       
   609         <xsl:text>        "</xsl:text>
       
   610         <xsl:value-of select="@value"/>
       
   611         <xsl:text>"</xsl:text>
       
   612         <xsl:if test="position()!=last()">
       
   613           <xsl:text>,</xsl:text>
       
   614         </xsl:if>
       
   615         <xsl:text>
       
   616 </xsl:text>
       
   617       </xsl:for-each>
       
   618       <xsl:text>    ],
       
   619 </xsl:text>
       
   620       <xsl:text>    indexes: [
       
   621 </xsl:text>
       
   622       <xsl:for-each select="$widget/path">
       
   623         <xsl:choose>
       
   624           <xsl:when test="not(@index)">
       
   625             <xsl:message terminate="no">
       
   626               <xsl:text>Widget </xsl:text>
       
   627               <xsl:value-of select="$widget/@type"/>
       
   628               <xsl:text> id="</xsl:text>
       
   629               <xsl:value-of select="$eltid"/>
       
   630               <xsl:text>" : No match for path "</xsl:text>
       
   631               <xsl:value-of select="@value"/>
       
   632               <xsl:text>" in HMI tree</xsl:text>
       
   633             </xsl:message>
       
   634           </xsl:when>
       
   635           <xsl:otherwise>
       
   636             <xsl:text>            </xsl:text>
       
   637             <xsl:value-of select="@index"/>
       
   638             <xsl:if test="position()!=last()">
       
   639               <xsl:text>,</xsl:text>
       
   640             </xsl:if>
       
   641             <xsl:text>
       
   642 </xsl:text>
       
   643           </xsl:otherwise>
       
   644         </xsl:choose>
       
   645       </xsl:for-each>
       
   646       <xsl:text>    ],
       
   647 </xsl:text>
       
   648       <xsl:text>    element: id("</xsl:text>
       
   649       <xsl:value-of select="@id"/>
       
   650       <xsl:text>"),
       
   651 </xsl:text>
       
   652       <xsl:apply-templates mode="widget_defs" select="$widget">
       
   653         <xsl:with-param name="hmi_element" select="."/>
       
   654       </xsl:apply-templates>
       
   655       <xsl:text>  }</xsl:text>
       
   656       <xsl:if test="position()!=last()">
       
   657         <xsl:text>,</xsl:text>
       
   658       </xsl:if>
       
   659       <xsl:text>
       
   660 </xsl:text>
       
   661     </xsl:for-each>
       
   662     <xsl:text>}
       
   663 </xsl:text>
       
   664     <xsl:text>
       
   665 </xsl:text>
       
   666     <xsl:text>var heartbeat_index = </xsl:text>
       
   667     <xsl:value-of select="$indexed_hmitree/*[@hmipath = '/HEARTBEAT']/@index"/>
       
   668     <xsl:text>;
       
   669 </xsl:text>
       
   670     <xsl:text>
       
   671 </xsl:text>
       
   672     <xsl:text>var hmitree_types = [
       
   673 </xsl:text>
       
   674     <xsl:for-each select="$indexed_hmitree/*">
       
   675       <xsl:text>    /* </xsl:text>
       
   676       <xsl:value-of select="@index"/>
       
   677       <xsl:text>  </xsl:text>
       
   678       <xsl:value-of select="@hmipath"/>
       
   679       <xsl:text> */ "</xsl:text>
       
   680       <xsl:value-of select="substring(local-name(), 5)"/>
       
   681       <xsl:text>"</xsl:text>
   546       <xsl:text>"</xsl:text>
   682       <xsl:if test="position()!=last()">
   547       <xsl:if test="position()!=last()">
   683         <xsl:text>,</xsl:text>
   548         <xsl:text>,</xsl:text>
   684       </xsl:if>
   549       </xsl:if>
   685       <xsl:text>
   550       <xsl:text>
   686 </xsl:text>
   551 </xsl:text>
   687     </xsl:for-each>
   552     </xsl:for-each>
   688     <xsl:text>]
   553     <xsl:text>    ],
   689 </xsl:text>
   554 </xsl:text>
   690     <xsl:text>
   555     <xsl:text>    indexes: [
   691 </xsl:text>
   556 </xsl:text>
   692     <xsl:text>var detachable_elements = {
   557     <xsl:for-each select="$widget/path">
   693 </xsl:text>
   558       <xsl:choose>
   694     <xsl:for-each select="$detachable_elements">
   559         <xsl:when test="not(@index)">
   695       <xsl:text>    "</xsl:text>
   560           <xsl:message terminate="no">
   696       <xsl:value-of select="@id"/>
   561             <xsl:text>Widget </xsl:text>
   697       <xsl:text>":[id("</xsl:text>
   562             <xsl:value-of select="$widget/@type"/>
   698       <xsl:value-of select="@id"/>
   563             <xsl:text> id="</xsl:text>
   699       <xsl:text>"), id("</xsl:text>
   564             <xsl:value-of select="$eltid"/>
   700       <xsl:value-of select="../@id"/>
   565             <xsl:text>" : No match for path "</xsl:text>
   701       <xsl:text>")]</xsl:text>
   566             <xsl:value-of select="@value"/>
   702       <xsl:if test="position()!=last()">
   567             <xsl:text>" in HMI tree</xsl:text>
   703         <xsl:text>,</xsl:text>
   568           </xsl:message>
   704       </xsl:if>
   569         </xsl:when>
   705       <xsl:text>
   570         <xsl:otherwise>
   706 </xsl:text>
   571           <xsl:text>            </xsl:text>
       
   572           <xsl:value-of select="@index"/>
       
   573           <xsl:if test="position()!=last()">
       
   574             <xsl:text>,</xsl:text>
       
   575           </xsl:if>
       
   576           <xsl:text>
       
   577 </xsl:text>
       
   578         </xsl:otherwise>
       
   579       </xsl:choose>
   707     </xsl:for-each>
   580     </xsl:for-each>
   708     <xsl:text>}
   581     <xsl:text>    ],
   709 </xsl:text>
   582 </xsl:text>
   710     <xsl:text>
   583     <xsl:text>    element: id("</xsl:text>
   711 </xsl:text>
   584     <xsl:value-of select="@id"/>
   712     <xsl:text>var page_desc = {
   585     <xsl:text>"),
   713 </xsl:text>
   586 </xsl:text>
   714     <xsl:apply-templates mode="page_desc" select="$hmi_pages"/>
   587     <xsl:apply-templates mode="widget_defs" select="$widget">
   715     <xsl:text>}
   588       <xsl:with-param name="hmi_element" select="."/>
   716 </xsl:text>
   589     </xsl:apply-templates>
   717     <xsl:text>
   590     <xsl:text>  }</xsl:text>
   718 </xsl:text>
   591     <xsl:if test="position()!=last()">
   719     <xsl:text>var default_page = "</xsl:text>
   592       <xsl:text>,</xsl:text>
   720     <xsl:value-of select="$default_page"/>
   593     </xsl:if>
   721     <xsl:text>";
   594     <xsl:text>
   722 </xsl:text>
       
   723     <xsl:text>var svg_root = id("</xsl:text>
       
   724     <xsl:value-of select="/svg:svg/@id"/>
       
   725     <xsl:text>");
       
   726 </xsl:text>
       
   727     <xsl:text>// svghmi.js
       
   728 </xsl:text>
       
   729     <xsl:text>
       
   730 </xsl:text>
       
   731     <xsl:text>var cache = hmitree_types.map(_ignored =&gt; undefined);
       
   732 </xsl:text>
       
   733     <xsl:text>var updates = {};
       
   734 </xsl:text>
       
   735     <xsl:text>
       
   736 </xsl:text>
       
   737     <xsl:text>function dispatch_value_to_widget(widget, index, value, oldval) {
       
   738 </xsl:text>
       
   739     <xsl:text>    try {
       
   740 </xsl:text>
       
   741     <xsl:text>        let idx = widget.offset ? index - widget.offset : index;
       
   742 </xsl:text>
       
   743     <xsl:text>        let idxidx = widget.indexes.indexOf(idx);
       
   744 </xsl:text>
       
   745     <xsl:text>        let d = widget.dispatch;
       
   746 </xsl:text>
       
   747     <xsl:text>        console.log(index, idx, idxidx, value);
       
   748 </xsl:text>
       
   749     <xsl:text>        if(typeof(d) == "function" &amp;&amp; idxidx == 0){
       
   750 </xsl:text>
       
   751     <xsl:text>            d.call(widget, value, oldval);
       
   752 </xsl:text>
       
   753     <xsl:text>        }
       
   754 </xsl:text>
       
   755     <xsl:text>        else if(typeof(d) == "object" &amp;&amp; d.length &gt;= idxidx){
       
   756 </xsl:text>
       
   757     <xsl:text>            d[idxidx].call(widget, value, oldval);
       
   758 </xsl:text>
       
   759     <xsl:text>        }
       
   760 </xsl:text>
       
   761     <xsl:text>        /* else dispatch_0, ..., dispatch_n ? */
       
   762 </xsl:text>
       
   763     <xsl:text>        /*else {
       
   764 </xsl:text>
       
   765     <xsl:text>            throw new Error("Dunno how to dispatch to widget at index = " + index);
       
   766 </xsl:text>
       
   767     <xsl:text>        }*/
       
   768 </xsl:text>
       
   769     <xsl:text>    } catch(err) {
       
   770 </xsl:text>
       
   771     <xsl:text>        console.log(err);
       
   772 </xsl:text>
       
   773     <xsl:text>    }
       
   774 </xsl:text>
       
   775     <xsl:text>}
       
   776 </xsl:text>
       
   777     <xsl:text>
       
   778 </xsl:text>
       
   779     <xsl:text>function dispatch_value(index, value) {
       
   780 </xsl:text>
       
   781     <xsl:text>    let widgets = subscribers[index];
       
   782 </xsl:text>
       
   783     <xsl:text>
       
   784 </xsl:text>
       
   785     <xsl:text>    let oldval = cache[index];
       
   786 </xsl:text>
       
   787     <xsl:text>    cache[index] = value;
       
   788 </xsl:text>
       
   789     <xsl:text>
       
   790 </xsl:text>
       
   791     <xsl:text>    if(widgets.size &gt; 0) {
       
   792 </xsl:text>
       
   793     <xsl:text>        for(let widget of widgets){
       
   794 </xsl:text>
       
   795     <xsl:text>            dispatch_value_to_widget(widget, index, value, oldval);
       
   796 </xsl:text>
       
   797     <xsl:text>        }
       
   798 </xsl:text>
       
   799     <xsl:text>    }
       
   800 </xsl:text>
       
   801     <xsl:text>};
       
   802 </xsl:text>
       
   803     <xsl:text>
       
   804 </xsl:text>
       
   805     <xsl:text>function init_widgets() {
       
   806 </xsl:text>
       
   807     <xsl:text>    Object.keys(hmi_widgets).forEach(function(id) {
       
   808 </xsl:text>
       
   809     <xsl:text>        let widget = hmi_widgets[id];
       
   810 </xsl:text>
       
   811     <xsl:text>        let init = widget.init;
       
   812 </xsl:text>
       
   813     <xsl:text>        if(typeof(init) == "function"){
       
   814 </xsl:text>
       
   815     <xsl:text>            try {
       
   816 </xsl:text>
       
   817     <xsl:text>                init.call(widget);
       
   818 </xsl:text>
       
   819     <xsl:text>            } catch(err) {
       
   820 </xsl:text>
       
   821     <xsl:text>                console.log(err);
       
   822 </xsl:text>
       
   823     <xsl:text>            }
       
   824 </xsl:text>
       
   825     <xsl:text>        }
       
   826 </xsl:text>
       
   827     <xsl:text>    });
       
   828 </xsl:text>
       
   829     <xsl:text>};
       
   830 </xsl:text>
       
   831     <xsl:text>
       
   832 </xsl:text>
       
   833     <xsl:text>// Open WebSocket to relative "/ws" address
       
   834 </xsl:text>
       
   835     <xsl:text>var ws = new WebSocket(window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws'));
       
   836 </xsl:text>
       
   837     <xsl:text>ws.binaryType = 'arraybuffer';
       
   838 </xsl:text>
       
   839     <xsl:text>
       
   840 </xsl:text>
       
   841     <xsl:text>const dvgetters = {
       
   842 </xsl:text>
       
   843     <xsl:text>    INT: (dv,offset) =&gt; [dv.getInt16(offset, true), 2],
       
   844 </xsl:text>
       
   845     <xsl:text>    BOOL: (dv,offset) =&gt; [dv.getInt8(offset, true), 1],
       
   846 </xsl:text>
       
   847     <xsl:text>    STRING: (dv, offset) =&gt; {
       
   848 </xsl:text>
       
   849     <xsl:text>        size = dv.getInt8(offset);
       
   850 </xsl:text>
       
   851     <xsl:text>        return [
       
   852 </xsl:text>
       
   853     <xsl:text>            String.fromCharCode.apply(null, new Uint8Array(
       
   854 </xsl:text>
       
   855     <xsl:text>                dv.buffer, /* original buffer */
       
   856 </xsl:text>
       
   857     <xsl:text>                offset + 1, /* string starts after size*/
       
   858 </xsl:text>
       
   859     <xsl:text>                size /* size of string */
       
   860 </xsl:text>
       
   861     <xsl:text>            )), size + 1]; /* total increment */
       
   862 </xsl:text>
       
   863     <xsl:text>    }
       
   864 </xsl:text>
       
   865     <xsl:text>};
       
   866 </xsl:text>
       
   867     <xsl:text>
       
   868 </xsl:text>
       
   869     <xsl:text>// Apply updates recieved through ws.onmessage to subscribed widgets
       
   870 </xsl:text>
       
   871     <xsl:text>function apply_updates() {
       
   872 </xsl:text>
       
   873     <xsl:text>    for(let index in updates){
       
   874 </xsl:text>
       
   875     <xsl:text>        // serving as a key, index becomes a string
       
   876 </xsl:text>
       
   877     <xsl:text>        // -&gt; pass Number(index) instead
       
   878 </xsl:text>
       
   879     <xsl:text>        dispatch_value(Number(index), updates[index]);
       
   880 </xsl:text>
       
   881     <xsl:text>        delete updates[index];
       
   882 </xsl:text>
       
   883     <xsl:text>    }
       
   884 </xsl:text>
       
   885     <xsl:text>}
       
   886 </xsl:text>
       
   887     <xsl:text>
       
   888 </xsl:text>
       
   889     <xsl:text>// Called on requestAnimationFrame, modifies DOM
       
   890 </xsl:text>
       
   891     <xsl:text>var requestAnimationFrameID = null;
       
   892 </xsl:text>
       
   893     <xsl:text>function animate() {
       
   894 </xsl:text>
       
   895     <xsl:text>    // Do the page swith if any one pending
       
   896 </xsl:text>
       
   897     <xsl:text>    if(current_subscribed_page != current_visible_page){
       
   898 </xsl:text>
       
   899     <xsl:text>        switch_visible_page(current_subscribed_page);
       
   900 </xsl:text>
       
   901     <xsl:text>    }
       
   902 </xsl:text>
       
   903     <xsl:text>    apply_updates();
       
   904 </xsl:text>
       
   905     <xsl:text>    requestAnimationFrameID = null;
       
   906 </xsl:text>
       
   907     <xsl:text>}
       
   908 </xsl:text>
       
   909     <xsl:text>
       
   910 </xsl:text>
       
   911     <xsl:text>function requestHMIAnimation() {
       
   912 </xsl:text>
       
   913     <xsl:text>    if(requestAnimationFrameID == null){
       
   914 </xsl:text>
       
   915     <xsl:text>        requestAnimationFrameID = window.requestAnimationFrame(animate);
       
   916 </xsl:text>
       
   917     <xsl:text>    }
       
   918 </xsl:text>
       
   919     <xsl:text>}
       
   920 </xsl:text>
       
   921     <xsl:text>
       
   922 </xsl:text>
       
   923     <xsl:text>// Message reception handler
       
   924 </xsl:text>
       
   925     <xsl:text>// Hash is verified and HMI values updates resulting from binary parsing
       
   926 </xsl:text>
       
   927     <xsl:text>// are stored until browser can compute next frame, DOM is left untouched
       
   928 </xsl:text>
       
   929     <xsl:text>ws.onmessage = function (evt) {
       
   930 </xsl:text>
       
   931     <xsl:text>
       
   932 </xsl:text>
       
   933     <xsl:text>    let data = evt.data;
       
   934 </xsl:text>
       
   935     <xsl:text>    let dv = new DataView(data);
       
   936 </xsl:text>
       
   937     <xsl:text>    let i = 0;
       
   938 </xsl:text>
       
   939     <xsl:text>    try {
       
   940 </xsl:text>
       
   941     <xsl:text>        for(let hash_int of hmi_hash) {
       
   942 </xsl:text>
       
   943     <xsl:text>            if(hash_int != dv.getUint8(i)){
       
   944 </xsl:text>
       
   945     <xsl:text>                throw new Error("Hash doesn't match");
       
   946 </xsl:text>
       
   947     <xsl:text>            };
       
   948 </xsl:text>
       
   949     <xsl:text>            i++;
       
   950 </xsl:text>
       
   951     <xsl:text>        };
       
   952 </xsl:text>
       
   953     <xsl:text>
       
   954 </xsl:text>
       
   955     <xsl:text>        while(i &lt; data.byteLength){
       
   956 </xsl:text>
       
   957     <xsl:text>            let index = dv.getUint32(i, true);
       
   958 </xsl:text>
       
   959     <xsl:text>            i += 4;
       
   960 </xsl:text>
       
   961     <xsl:text>            let iectype = hmitree_types[index];
       
   962 </xsl:text>
       
   963     <xsl:text>            if(iectype != undefined){
       
   964 </xsl:text>
       
   965     <xsl:text>                let dvgetter = dvgetters[iectype];
       
   966 </xsl:text>
       
   967     <xsl:text>                let [value, bytesize] = dvgetter(dv,i);
       
   968 </xsl:text>
       
   969     <xsl:text>                updates[index] = value;
       
   970 </xsl:text>
       
   971     <xsl:text>                i += bytesize;
       
   972 </xsl:text>
       
   973     <xsl:text>            } else {
       
   974 </xsl:text>
       
   975     <xsl:text>                throw new Error("Unknown index "+index);
       
   976 </xsl:text>
       
   977     <xsl:text>            }
       
   978 </xsl:text>
       
   979     <xsl:text>        };
       
   980 </xsl:text>
       
   981     <xsl:text>        // register for rendering on next frame, since there are updates
       
   982 </xsl:text>
       
   983     <xsl:text>        requestHMIAnimation();
       
   984 </xsl:text>
       
   985     <xsl:text>    } catch(err) {
       
   986 </xsl:text>
       
   987     <xsl:text>        // 1003 is for "Unsupported Data"
       
   988 </xsl:text>
       
   989     <xsl:text>        // ws.close(1003, err.message);
       
   990 </xsl:text>
       
   991     <xsl:text>
       
   992 </xsl:text>
       
   993     <xsl:text>        // TODO : remove debug alert ?
       
   994 </xsl:text>
       
   995     <xsl:text>        alert("Error : "+err.message+"\nHMI will be reloaded.");
       
   996 </xsl:text>
       
   997     <xsl:text>
       
   998 </xsl:text>
       
   999     <xsl:text>        // force reload ignoring cache
       
  1000 </xsl:text>
       
  1001     <xsl:text>        location.reload(true);
       
  1002 </xsl:text>
       
  1003     <xsl:text>    }
       
  1004 </xsl:text>
       
  1005     <xsl:text>};
       
  1006 </xsl:text>
       
  1007     <xsl:text>
       
  1008 </xsl:text>
       
  1009     <xsl:text>
       
  1010 </xsl:text>
       
  1011     <xsl:text>function send_blob(data) {
       
  1012 </xsl:text>
       
  1013     <xsl:text>    if(data.length &gt; 0) {
       
  1014 </xsl:text>
       
  1015     <xsl:text>        ws.send(new Blob([new Uint8Array(hmi_hash)].concat(data)));
       
  1016 </xsl:text>
       
  1017     <xsl:text>    };
       
  1018 </xsl:text>
       
  1019     <xsl:text>};
       
  1020 </xsl:text>
       
  1021     <xsl:text>
       
  1022 </xsl:text>
       
  1023     <xsl:text>const typedarray_types = {
       
  1024 </xsl:text>
       
  1025     <xsl:text>    INT: (number) =&gt; new Int16Array([number]),
       
  1026 </xsl:text>
       
  1027     <xsl:text>    BOOL: (truth) =&gt; new Int16Array([truth]),
       
  1028 </xsl:text>
       
  1029     <xsl:text>    STRING: (str) =&gt; {
       
  1030 </xsl:text>
       
  1031     <xsl:text>        // beremiz default string max size is 128
       
  1032 </xsl:text>
       
  1033     <xsl:text>        str = str.slice(0,128);
       
  1034 </xsl:text>
       
  1035     <xsl:text>        binary = new Uint8Array(str.length + 1);
       
  1036 </xsl:text>
       
  1037     <xsl:text>        binary[0] = str.length;
       
  1038 </xsl:text>
       
  1039     <xsl:text>        for(var i = 0; i &lt; str.length; i++){
       
  1040 </xsl:text>
       
  1041     <xsl:text>            binary[i+1] = str.charCodeAt(i);
       
  1042 </xsl:text>
       
  1043     <xsl:text>        }
       
  1044 </xsl:text>
       
  1045     <xsl:text>        return binary;
       
  1046 </xsl:text>
       
  1047     <xsl:text>    }
       
  1048 </xsl:text>
       
  1049     <xsl:text>    /* TODO */
       
  1050 </xsl:text>
       
  1051     <xsl:text>};
       
  1052 </xsl:text>
       
  1053     <xsl:text>
       
  1054 </xsl:text>
       
  1055     <xsl:text>function send_reset() {
       
  1056 </xsl:text>
       
  1057     <xsl:text>    send_blob(new Uint8Array([1])); /* reset = 1 */
       
  1058 </xsl:text>
       
  1059     <xsl:text>};
       
  1060 </xsl:text>
       
  1061     <xsl:text>
       
  1062 </xsl:text>
       
  1063     <xsl:text>// subscription state, as it should be in hmi server
       
  1064 </xsl:text>
       
  1065     <xsl:text>// hmitree indexed array of integers
       
  1066 </xsl:text>
       
  1067     <xsl:text>var subscriptions =  hmitree_types.map(_ignored =&gt; 0);
       
  1068 </xsl:text>
       
  1069     <xsl:text>
       
  1070 </xsl:text>
       
  1071     <xsl:text>// subscription state as needed by widget now
       
  1072 </xsl:text>
       
  1073     <xsl:text>// hmitree indexed array of Sets of widgets objects
       
  1074 </xsl:text>
       
  1075     <xsl:text>var subscribers = hmitree_types.map(_ignored =&gt; new Set());
       
  1076 </xsl:text>
       
  1077     <xsl:text>
       
  1078 </xsl:text>
       
  1079     <xsl:text>// artificially subscribe the watchdog widget to "/heartbeat" hmi variable
       
  1080 </xsl:text>
       
  1081     <xsl:text>// Since dispatch directly calls change_hmi_value,
       
  1082 </xsl:text>
       
  1083     <xsl:text>// PLC will periodically send variable at given frequency
       
  1084 </xsl:text>
       
  1085     <xsl:text>subscribers[heartbeat_index].add({
       
  1086 </xsl:text>
       
  1087     <xsl:text>    /* type: "Watchdog", */
       
  1088 </xsl:text>
       
  1089     <xsl:text>    frequency: 1,
       
  1090 </xsl:text>
       
  1091     <xsl:text>    indexes: [heartbeat_index],
       
  1092 </xsl:text>
       
  1093     <xsl:text>    dispatch: function(value) {
       
  1094 </xsl:text>
       
  1095     <xsl:text>        // console.log("Heartbeat" + value);
       
  1096 </xsl:text>
       
  1097     <xsl:text>        change_hmi_value(heartbeat_index, "+1");
       
  1098 </xsl:text>
       
  1099     <xsl:text>    }
       
  1100 </xsl:text>
       
  1101     <xsl:text>});
       
  1102 </xsl:text>
       
  1103     <xsl:text>
       
  1104 </xsl:text>
       
  1105     <xsl:text>function update_subscriptions() {
       
  1106 </xsl:text>
       
  1107     <xsl:text>    let delta = [];
       
  1108 </xsl:text>
       
  1109     <xsl:text>    for(let index = 0; index &lt; subscribers.length; index++){
       
  1110 </xsl:text>
       
  1111     <xsl:text>        let widgets = subscribers[index];
       
  1112 </xsl:text>
       
  1113     <xsl:text>
       
  1114 </xsl:text>
       
  1115     <xsl:text>        // periods are in ms
       
  1116 </xsl:text>
       
  1117     <xsl:text>        let previous_period = subscriptions[index];
       
  1118 </xsl:text>
       
  1119     <xsl:text>
       
  1120 </xsl:text>
       
  1121     <xsl:text>        // subscribing with a zero period is unsubscribing
       
  1122 </xsl:text>
       
  1123     <xsl:text>        let new_period = 0;
       
  1124 </xsl:text>
       
  1125     <xsl:text>        if(widgets.size &gt; 0) {
       
  1126 </xsl:text>
       
  1127     <xsl:text>            let maxfreq = 0;
       
  1128 </xsl:text>
       
  1129     <xsl:text>            for(let widget of widgets)
       
  1130 </xsl:text>
       
  1131     <xsl:text>                if(maxfreq &lt; widget.frequency)
       
  1132 </xsl:text>
       
  1133     <xsl:text>                    maxfreq = widget.frequency;
       
  1134 </xsl:text>
       
  1135     <xsl:text>
       
  1136 </xsl:text>
       
  1137     <xsl:text>            if(maxfreq != 0)
       
  1138 </xsl:text>
       
  1139     <xsl:text>                new_period = 1000/maxfreq;
       
  1140 </xsl:text>
       
  1141     <xsl:text>        }
       
  1142 </xsl:text>
       
  1143     <xsl:text>
       
  1144 </xsl:text>
       
  1145     <xsl:text>        if(previous_period != new_period) {
       
  1146 </xsl:text>
       
  1147     <xsl:text>            subscriptions[index] = new_period;
       
  1148 </xsl:text>
       
  1149     <xsl:text>            delta.push(
       
  1150 </xsl:text>
       
  1151     <xsl:text>                new Uint8Array([2]), /* subscribe = 2 */
       
  1152 </xsl:text>
       
  1153     <xsl:text>                new Uint32Array([index]),
       
  1154 </xsl:text>
       
  1155     <xsl:text>                new Uint16Array([new_period]));
       
  1156 </xsl:text>
       
  1157     <xsl:text>        }
       
  1158 </xsl:text>
       
  1159     <xsl:text>    }
       
  1160 </xsl:text>
       
  1161     <xsl:text>    send_blob(delta);
       
  1162 </xsl:text>
       
  1163     <xsl:text>};
       
  1164 </xsl:text>
       
  1165     <xsl:text>
       
  1166 </xsl:text>
       
  1167     <xsl:text>function send_hmi_value(index, value) {
       
  1168 </xsl:text>
       
  1169     <xsl:text>    let iectype = hmitree_types[index];
       
  1170 </xsl:text>
       
  1171     <xsl:text>    let tobinary = typedarray_types[iectype];
       
  1172 </xsl:text>
       
  1173     <xsl:text>    send_blob([
       
  1174 </xsl:text>
       
  1175     <xsl:text>        new Uint8Array([0]),  /* setval = 0 */
       
  1176 </xsl:text>
       
  1177     <xsl:text>        new Uint32Array([index]),
       
  1178 </xsl:text>
       
  1179     <xsl:text>        tobinary(value)]);
       
  1180 </xsl:text>
       
  1181     <xsl:text>
       
  1182 </xsl:text>
       
  1183     <xsl:text>    cache[index] = value;
       
  1184 </xsl:text>
       
  1185     <xsl:text>};
       
  1186 </xsl:text>
       
  1187     <xsl:text>
       
  1188 </xsl:text>
       
  1189     <xsl:text>function change_hmi_value(index, opstr) {
       
  1190 </xsl:text>
       
  1191     <xsl:text>    let op = opstr[0];
       
  1192 </xsl:text>
       
  1193     <xsl:text>    let given_val = opstr.slice(1);
       
  1194 </xsl:text>
       
  1195     <xsl:text>    let old_val = cache[index]
       
  1196 </xsl:text>
       
  1197     <xsl:text>    let new_val;
       
  1198 </xsl:text>
       
  1199     <xsl:text>    switch(op){
       
  1200 </xsl:text>
       
  1201     <xsl:text>      case "=":
       
  1202 </xsl:text>
       
  1203     <xsl:text>        eval("new_val"+opstr);
       
  1204 </xsl:text>
       
  1205     <xsl:text>        break;
       
  1206 </xsl:text>
       
  1207     <xsl:text>      case "+":
       
  1208 </xsl:text>
       
  1209     <xsl:text>      case "-":
       
  1210 </xsl:text>
       
  1211     <xsl:text>      case "*":
       
  1212 </xsl:text>
       
  1213     <xsl:text>      case "/":
       
  1214 </xsl:text>
       
  1215     <xsl:text>        if(old_val != undefined)
       
  1216 </xsl:text>
       
  1217     <xsl:text>            new_val = eval("old_val"+opstr);
       
  1218 </xsl:text>
       
  1219     <xsl:text>        break;
       
  1220 </xsl:text>
       
  1221     <xsl:text>    }
       
  1222 </xsl:text>
       
  1223     <xsl:text>    if(new_val != undefined &amp;&amp; old_val != new_val)
       
  1224 </xsl:text>
       
  1225     <xsl:text>        send_hmi_value(index, new_val);
       
  1226 </xsl:text>
       
  1227     <xsl:text>    return new_val;
       
  1228 </xsl:text>
       
  1229     <xsl:text>}
       
  1230 </xsl:text>
       
  1231     <xsl:text>
       
  1232 </xsl:text>
       
  1233     <xsl:text>var current_visible_page;
       
  1234 </xsl:text>
       
  1235     <xsl:text>var current_subscribed_page;
       
  1236 </xsl:text>
       
  1237     <xsl:text>
       
  1238 </xsl:text>
       
  1239     <xsl:text>function prepare_svg() {
       
  1240 </xsl:text>
       
  1241     <xsl:text>    for(let eltid in detachable_elements){
       
  1242 </xsl:text>
       
  1243     <xsl:text>        let [element,parent] = detachable_elements[eltid];
       
  1244 </xsl:text>
       
  1245     <xsl:text>        parent.removeChild(element);
       
  1246 </xsl:text>
       
  1247     <xsl:text>    }
       
  1248 </xsl:text>
       
  1249     <xsl:text>};
       
  1250 </xsl:text>
       
  1251     <xsl:text>
       
  1252 </xsl:text>
       
  1253     <xsl:text>function switch_page(page_name, page_index) {
       
  1254 </xsl:text>
       
  1255     <xsl:text>    if(current_subscribed_page != current_visible_page){
       
  1256 </xsl:text>
       
  1257     <xsl:text>        /* page switch already going */
       
  1258 </xsl:text>
       
  1259     <xsl:text>        /* TODO LOG ERROR */
       
  1260 </xsl:text>
       
  1261     <xsl:text>        return;
       
  1262 </xsl:text>
       
  1263     <xsl:text>    } else if(page_name == current_visible_page){
       
  1264 </xsl:text>
       
  1265     <xsl:text>        /* already in that page */
       
  1266 </xsl:text>
       
  1267     <xsl:text>        /* TODO LOG ERROR */
       
  1268 </xsl:text>
       
  1269     <xsl:text>        return;
       
  1270 </xsl:text>
       
  1271     <xsl:text>    }
       
  1272 </xsl:text>
       
  1273     <xsl:text>    switch_subscribed_page(page_name, page_index);
       
  1274 </xsl:text>
       
  1275     <xsl:text>};
       
  1276 </xsl:text>
       
  1277     <xsl:text>
       
  1278 </xsl:text>
       
  1279     <xsl:text>function* chain(a,b){
       
  1280 </xsl:text>
       
  1281     <xsl:text>    yield* a;
       
  1282 </xsl:text>
       
  1283     <xsl:text>    yield* b;
       
  1284 </xsl:text>
       
  1285     <xsl:text>};
       
  1286 </xsl:text>
       
  1287     <xsl:text>
       
  1288 </xsl:text>
       
  1289     <xsl:text>function switch_subscribed_page(page_name, page_index) {
       
  1290 </xsl:text>
       
  1291     <xsl:text>    let old_desc = page_desc[current_subscribed_page];
       
  1292 </xsl:text>
       
  1293     <xsl:text>    let new_desc = page_desc[page_name];
       
  1294 </xsl:text>
       
  1295     <xsl:text>
       
  1296 </xsl:text>
       
  1297     <xsl:text>    if(new_desc == undefined){
       
  1298 </xsl:text>
       
  1299     <xsl:text>        /* TODO LOG ERROR */
       
  1300 </xsl:text>
       
  1301     <xsl:text>        return;
       
  1302 </xsl:text>
       
  1303     <xsl:text>    }
       
  1304 </xsl:text>
       
  1305     <xsl:text>
       
  1306 </xsl:text>
       
  1307     <xsl:text>    if(page_index == undefined){
       
  1308 </xsl:text>
       
  1309     <xsl:text>        page_index = new_desc.page_index;
       
  1310 </xsl:text>
       
  1311     <xsl:text>    }
       
  1312 </xsl:text>
       
  1313     <xsl:text>
       
  1314 </xsl:text>
       
  1315     <xsl:text>    if(old_desc){
       
  1316 </xsl:text>
       
  1317     <xsl:text>        for(let widget of old_desc.absolute_widgets){
       
  1318 </xsl:text>
       
  1319     <xsl:text>            /* remove subsribers */
       
  1320 </xsl:text>
       
  1321     <xsl:text>            for(let index of widget.indexes){
       
  1322 </xsl:text>
       
  1323     <xsl:text>                subscribers[index].delete(widget);
       
  1324 </xsl:text>
       
  1325     <xsl:text>            }
       
  1326 </xsl:text>
       
  1327     <xsl:text>        }
       
  1328 </xsl:text>
       
  1329     <xsl:text>        for(let widget of old_desc.relative_widgets){
       
  1330 </xsl:text>
       
  1331     <xsl:text>            /* remove subsribers */
       
  1332 </xsl:text>
       
  1333     <xsl:text>            for(let index of widget.indexes){
       
  1334 </xsl:text>
       
  1335     <xsl:text>                let idx = widget.offset ? index + widget.offset : index;
       
  1336 </xsl:text>
       
  1337     <xsl:text>                subscribers[idx].delete(widget);
       
  1338 </xsl:text>
       
  1339     <xsl:text>            }
       
  1340 </xsl:text>
       
  1341     <xsl:text>            /* lose the offset */
       
  1342 </xsl:text>
       
  1343     <xsl:text>            delete widget.offset;
       
  1344 </xsl:text>
       
  1345     <xsl:text>        }
       
  1346 </xsl:text>
       
  1347     <xsl:text>    }
       
  1348 </xsl:text>
       
  1349     <xsl:text>    for(let widget of new_desc.absolute_widgets){
       
  1350 </xsl:text>
       
  1351     <xsl:text>        /* add widget's subsribers */
       
  1352 </xsl:text>
       
  1353     <xsl:text>        for(let index of widget.indexes){
       
  1354 </xsl:text>
       
  1355     <xsl:text>            subscribers[index].add(widget);
       
  1356 </xsl:text>
       
  1357     <xsl:text>        }
       
  1358 </xsl:text>
       
  1359     <xsl:text>    }
       
  1360 </xsl:text>
       
  1361     <xsl:text>    var new_offset = page_index == undefined ? 0 : page_index - new_desc.page_index;
       
  1362 </xsl:text>
       
  1363     <xsl:text>    for(let widget of new_desc.relative_widgets){
       
  1364 </xsl:text>
       
  1365     <xsl:text>        /* set the offset because relative */
       
  1366 </xsl:text>
       
  1367     <xsl:text>        widget.offset = new_offset;
       
  1368 </xsl:text>
       
  1369     <xsl:text>        /* add widget's subsribers */
       
  1370 </xsl:text>
       
  1371     <xsl:text>        for(let index of widget.indexes){
       
  1372 </xsl:text>
       
  1373     <xsl:text>            subscribers[index + new_offset].add(widget);
       
  1374 </xsl:text>
       
  1375     <xsl:text>        }
       
  1376 </xsl:text>
       
  1377     <xsl:text>    }
       
  1378 </xsl:text>
       
  1379     <xsl:text>
       
  1380 </xsl:text>
       
  1381     <xsl:text>    update_subscriptions();
       
  1382 </xsl:text>
       
  1383     <xsl:text>
       
  1384 </xsl:text>
       
  1385     <xsl:text>    current_subscribed_page = page_name;
       
  1386 </xsl:text>
       
  1387     <xsl:text>
       
  1388 </xsl:text>
       
  1389     <xsl:text>    requestHMIAnimation();
       
  1390 </xsl:text>
       
  1391     <xsl:text>}
       
  1392 </xsl:text>
       
  1393     <xsl:text>
       
  1394 </xsl:text>
       
  1395     <xsl:text>function switch_visible_page(page_name) {
       
  1396 </xsl:text>
       
  1397     <xsl:text>
       
  1398 </xsl:text>
       
  1399     <xsl:text>    let old_desc = page_desc[current_visible_page];
       
  1400 </xsl:text>
       
  1401     <xsl:text>    let new_desc = page_desc[page_name];
       
  1402 </xsl:text>
       
  1403     <xsl:text>
       
  1404 </xsl:text>
       
  1405     <xsl:text>    if(old_desc){
       
  1406 </xsl:text>
       
  1407     <xsl:text>        for(let eltid in old_desc.required_detachables){
       
  1408 </xsl:text>
       
  1409     <xsl:text>            if(!(eltid in new_desc.required_detachables)){
       
  1410 </xsl:text>
       
  1411     <xsl:text>                let [element, parent] = old_desc.required_detachables[eltid];
       
  1412 </xsl:text>
       
  1413     <xsl:text>                parent.removeChild(element);
       
  1414 </xsl:text>
       
  1415     <xsl:text>            }
       
  1416 </xsl:text>
       
  1417     <xsl:text>        }
       
  1418 </xsl:text>
       
  1419     <xsl:text>        for(let eltid in new_desc.required_detachables){
       
  1420 </xsl:text>
       
  1421     <xsl:text>            if(!(eltid in old_desc.required_detachables)){
       
  1422 </xsl:text>
       
  1423     <xsl:text>                let [element, parent] = new_desc.required_detachables[eltid];
       
  1424 </xsl:text>
       
  1425     <xsl:text>                parent.appendChild(element);
       
  1426 </xsl:text>
       
  1427     <xsl:text>            }
       
  1428 </xsl:text>
       
  1429     <xsl:text>        }
       
  1430 </xsl:text>
       
  1431     <xsl:text>    }else{
       
  1432 </xsl:text>
       
  1433     <xsl:text>        for(let eltid in new_desc.required_detachables){
       
  1434 </xsl:text>
       
  1435     <xsl:text>            let [element, parent] = new_desc.required_detachables[eltid];
       
  1436 </xsl:text>
       
  1437     <xsl:text>            parent.appendChild(element);
       
  1438 </xsl:text>
       
  1439     <xsl:text>        }
       
  1440 </xsl:text>
       
  1441     <xsl:text>    }
       
  1442 </xsl:text>
       
  1443     <xsl:text>
       
  1444 </xsl:text>
       
  1445     <xsl:text>    for(let widget of chain(new_desc.absolute_widgets,new_desc.relative_widgets)){
       
  1446 </xsl:text>
       
  1447     <xsl:text>        for(let index of widget.indexes){
       
  1448 </xsl:text>
       
  1449     <xsl:text>            /* dispatch current cache in newly opened page widgets */
       
  1450 </xsl:text>
       
  1451     <xsl:text>            let cached_val = cache[index];
       
  1452 </xsl:text>
       
  1453     <xsl:text>            if(cached_val != undefined)
       
  1454 </xsl:text>
       
  1455     <xsl:text>                dispatch_value_to_widget(widget, index, cached_val, cached_val);
       
  1456 </xsl:text>
       
  1457     <xsl:text>        }
       
  1458 </xsl:text>
       
  1459     <xsl:text>    }
       
  1460 </xsl:text>
       
  1461     <xsl:text>
       
  1462 </xsl:text>
       
  1463     <xsl:text>    svg_root.setAttribute('viewBox',new_desc.bbox.join(" "));
       
  1464 </xsl:text>
       
  1465     <xsl:text>    current_visible_page = page_name;
       
  1466 </xsl:text>
       
  1467     <xsl:text>};
       
  1468 </xsl:text>
       
  1469     <xsl:text>
       
  1470 </xsl:text>
       
  1471     <xsl:text>
       
  1472 </xsl:text>
       
  1473     <xsl:text>// Once connection established
       
  1474 </xsl:text>
       
  1475     <xsl:text>ws.onopen = function (evt) {
       
  1476 </xsl:text>
       
  1477     <xsl:text>    init_widgets();
       
  1478 </xsl:text>
       
  1479     <xsl:text>    send_reset();
       
  1480 </xsl:text>
       
  1481     <xsl:text>    // show main page
       
  1482 </xsl:text>
       
  1483     <xsl:text>    prepare_svg();
       
  1484 </xsl:text>
       
  1485     <xsl:text>    switch_page(default_page);
       
  1486 </xsl:text>
       
  1487     <xsl:text>};
       
  1488 </xsl:text>
       
  1489     <xsl:text>
       
  1490 </xsl:text>
       
  1491     <xsl:text>ws.onclose = function (evt) {
       
  1492 </xsl:text>
       
  1493     <xsl:text>    // TODO : add visible notification while waiting for reload
       
  1494 </xsl:text>
       
  1495     <xsl:text>    console.log("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+" Reload in 10s.");
       
  1496 </xsl:text>
       
  1497     <xsl:text>    // TODO : re-enable auto reload when not in debug
       
  1498 </xsl:text>
       
  1499     <xsl:text>    //window.setTimeout(() =&gt; location.reload(true), 10000);
       
  1500 </xsl:text>
       
  1501     <xsl:text>    alert("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+".");
       
  1502 </xsl:text>
       
  1503     <xsl:text>
       
  1504 </xsl:text>
       
  1505     <xsl:text>};
       
  1506 </xsl:text>
       
  1507     <xsl:text>//})();
       
  1508 </xsl:text>
   595 </xsl:text>
  1509   </xsl:template>
   596   </xsl:template>
  1510   <xsl:template name="defs_by_labels">
   597   <xsl:template name="defs_by_labels">
  1511     <xsl:param name="labels" select="''"/>
   598     <xsl:param name="labels" select="''"/>
  1512     <xsl:param name="mandatory" select="'yes'"/>
   599     <xsl:param name="mandatory" select="'yes'"/>
  1534           <xsl:text>"),
   621           <xsl:text>"),
  1535 </xsl:text>
   622 </xsl:text>
  1536         </xsl:otherwise>
   623         </xsl:otherwise>
  1537       </xsl:choose>
   624       </xsl:choose>
  1538     </xsl:for-each>
   625     </xsl:for-each>
       
   626   </xsl:template>
       
   627   <xsl:template match="/">
       
   628     <xsl:comment>
       
   629       <xsl:text>Made with SVGHMI. https://beremiz.org</xsl:text>
       
   630     </xsl:comment>
       
   631     <xsl:comment>
       
   632       <xsl:text>
       
   633 </xsl:text>
       
   634       <xsl:text>debug_hmitree:
       
   635 </xsl:text>
       
   636       <xsl:call-template name="debug_hmitree"/>
       
   637       <xsl:text>
       
   638 </xsl:text>
       
   639     </xsl:comment>
       
   640     <xsl:comment>
       
   641       <xsl:text>
       
   642 </xsl:text>
       
   643       <xsl:text>debug_geometry:
       
   644 </xsl:text>
       
   645       <xsl:call-template name="debug_geometry"/>
       
   646       <xsl:text>
       
   647 </xsl:text>
       
   648     </xsl:comment>
       
   649     <xsl:comment>
       
   650       <xsl:text>
       
   651 </xsl:text>
       
   652       <xsl:text>debug_detachables:
       
   653 </xsl:text>
       
   654       <xsl:call-template name="debug_detachables"/>
       
   655       <xsl:text>
       
   656 </xsl:text>
       
   657     </xsl:comment>
       
   658     <xsl:comment>
       
   659       <xsl:text>
       
   660 </xsl:text>
       
   661       <xsl:text>debug_unlink:
       
   662 </xsl:text>
       
   663       <xsl:call-template name="debug_unlink"/>
       
   664       <xsl:text>
       
   665 </xsl:text>
       
   666     </xsl:comment>
       
   667     <html xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/1999/xhtml">
       
   668       <head/>
       
   669       <body style="margin:0;overflow:hidden;">
       
   670         <xsl:copy-of select="$result_svg"/>
       
   671         <script>
       
   672           <xsl:call-template name="scripts"/>
       
   673         </script>
       
   674       </body>
       
   675     </html>
       
   676   </xsl:template>
       
   677   <xsl:template name="scripts">
       
   678     <xsl:text>//(function(){
       
   679 </xsl:text>
       
   680     <xsl:text>
       
   681 </xsl:text>
       
   682     <xsl:text>id = idstr =&gt; document.getElementById(idstr);
       
   683 </xsl:text>
       
   684     <xsl:text>
       
   685 </xsl:text>
       
   686     <xsl:text>var hmi_hash = [</xsl:text>
       
   687     <xsl:value-of select="$hmitree/@hash"/>
       
   688     <xsl:text>]; 
       
   689 </xsl:text>
       
   690     <xsl:text>var hmi_widgets = {
       
   691 </xsl:text>
       
   692     <xsl:apply-templates mode="hmi_elements" select="$hmi_elements"/>
       
   693     <xsl:text>}
       
   694 </xsl:text>
       
   695     <xsl:text>
       
   696 </xsl:text>
       
   697     <xsl:text>var heartbeat_index = </xsl:text>
       
   698     <xsl:value-of select="$indexed_hmitree/*[@hmipath = '/HEARTBEAT']/@index"/>
       
   699     <xsl:text>;
       
   700 </xsl:text>
       
   701     <xsl:text>
       
   702 </xsl:text>
       
   703     <xsl:text>var hmitree_types = [
       
   704 </xsl:text>
       
   705     <xsl:for-each select="$indexed_hmitree/*">
       
   706       <xsl:text>    /* </xsl:text>
       
   707       <xsl:value-of select="@index"/>
       
   708       <xsl:text>  </xsl:text>
       
   709       <xsl:value-of select="@hmipath"/>
       
   710       <xsl:text> */ "</xsl:text>
       
   711       <xsl:value-of select="substring(local-name(), 5)"/>
       
   712       <xsl:text>"</xsl:text>
       
   713       <xsl:if test="position()!=last()">
       
   714         <xsl:text>,</xsl:text>
       
   715       </xsl:if>
       
   716       <xsl:text>
       
   717 </xsl:text>
       
   718     </xsl:for-each>
       
   719     <xsl:text>]
       
   720 </xsl:text>
       
   721     <xsl:text>
       
   722 </xsl:text>
       
   723     <xsl:text>var detachable_elements = {
       
   724 </xsl:text>
       
   725     <xsl:for-each select="$detachable_elements">
       
   726       <xsl:text>    "</xsl:text>
       
   727       <xsl:value-of select="@id"/>
       
   728       <xsl:text>":[id("</xsl:text>
       
   729       <xsl:value-of select="@id"/>
       
   730       <xsl:text>"), id("</xsl:text>
       
   731       <xsl:value-of select="../@id"/>
       
   732       <xsl:text>")]</xsl:text>
       
   733       <xsl:if test="position()!=last()">
       
   734         <xsl:text>,</xsl:text>
       
   735       </xsl:if>
       
   736       <xsl:text>
       
   737 </xsl:text>
       
   738     </xsl:for-each>
       
   739     <xsl:text>}
       
   740 </xsl:text>
       
   741     <xsl:text>
       
   742 </xsl:text>
       
   743     <xsl:text>var page_desc = {
       
   744 </xsl:text>
       
   745     <xsl:apply-templates mode="page_desc" select="$hmi_pages"/>
       
   746     <xsl:text>}
       
   747 </xsl:text>
       
   748     <xsl:text>
       
   749 </xsl:text>
       
   750     <xsl:text>var default_page = "</xsl:text>
       
   751     <xsl:value-of select="$default_page"/>
       
   752     <xsl:text>";
       
   753 </xsl:text>
       
   754     <xsl:text>var svg_root = id("</xsl:text>
       
   755     <xsl:value-of select="/svg:svg/@id"/>
       
   756     <xsl:text>");
       
   757 </xsl:text>
       
   758     <xsl:text>// svghmi.js
       
   759 </xsl:text>
       
   760     <xsl:text>
       
   761 </xsl:text>
       
   762     <xsl:text>var cache = hmitree_types.map(_ignored =&gt; undefined);
       
   763 </xsl:text>
       
   764     <xsl:text>var updates = {};
       
   765 </xsl:text>
       
   766     <xsl:text>
       
   767 </xsl:text>
       
   768     <xsl:text>function dispatch_value_to_widget(widget, index, value, oldval) {
       
   769 </xsl:text>
       
   770     <xsl:text>    try {
       
   771 </xsl:text>
       
   772     <xsl:text>        let idx = widget.offset ? index - widget.offset : index;
       
   773 </xsl:text>
       
   774     <xsl:text>        let idxidx = widget.indexes.indexOf(idx);
       
   775 </xsl:text>
       
   776     <xsl:text>        let d = widget.dispatch;
       
   777 </xsl:text>
       
   778     <xsl:text>        console.log(index, idx, idxidx, value);
       
   779 </xsl:text>
       
   780     <xsl:text>        if(typeof(d) == "function" &amp;&amp; idxidx == 0){
       
   781 </xsl:text>
       
   782     <xsl:text>            d.call(widget, value, oldval);
       
   783 </xsl:text>
       
   784     <xsl:text>        }
       
   785 </xsl:text>
       
   786     <xsl:text>        else if(typeof(d) == "object" &amp;&amp; d.length &gt;= idxidx){
       
   787 </xsl:text>
       
   788     <xsl:text>            d[idxidx].call(widget, value, oldval);
       
   789 </xsl:text>
       
   790     <xsl:text>        }
       
   791 </xsl:text>
       
   792     <xsl:text>        /* else dispatch_0, ..., dispatch_n ? */
       
   793 </xsl:text>
       
   794     <xsl:text>        /*else {
       
   795 </xsl:text>
       
   796     <xsl:text>            throw new Error("Dunno how to dispatch to widget at index = " + index);
       
   797 </xsl:text>
       
   798     <xsl:text>        }*/
       
   799 </xsl:text>
       
   800     <xsl:text>    } catch(err) {
       
   801 </xsl:text>
       
   802     <xsl:text>        console.log(err);
       
   803 </xsl:text>
       
   804     <xsl:text>    }
       
   805 </xsl:text>
       
   806     <xsl:text>}
       
   807 </xsl:text>
       
   808     <xsl:text>
       
   809 </xsl:text>
       
   810     <xsl:text>function dispatch_value(index, value) {
       
   811 </xsl:text>
       
   812     <xsl:text>    let widgets = subscribers[index];
       
   813 </xsl:text>
       
   814     <xsl:text>
       
   815 </xsl:text>
       
   816     <xsl:text>    let oldval = cache[index];
       
   817 </xsl:text>
       
   818     <xsl:text>    cache[index] = value;
       
   819 </xsl:text>
       
   820     <xsl:text>
       
   821 </xsl:text>
       
   822     <xsl:text>    if(widgets.size &gt; 0) {
       
   823 </xsl:text>
       
   824     <xsl:text>        for(let widget of widgets){
       
   825 </xsl:text>
       
   826     <xsl:text>            dispatch_value_to_widget(widget, index, value, oldval);
       
   827 </xsl:text>
       
   828     <xsl:text>        }
       
   829 </xsl:text>
       
   830     <xsl:text>    }
       
   831 </xsl:text>
       
   832     <xsl:text>};
       
   833 </xsl:text>
       
   834     <xsl:text>
       
   835 </xsl:text>
       
   836     <xsl:text>function init_widgets() {
       
   837 </xsl:text>
       
   838     <xsl:text>    Object.keys(hmi_widgets).forEach(function(id) {
       
   839 </xsl:text>
       
   840     <xsl:text>        let widget = hmi_widgets[id];
       
   841 </xsl:text>
       
   842     <xsl:text>        let init = widget.init;
       
   843 </xsl:text>
       
   844     <xsl:text>        if(typeof(init) == "function"){
       
   845 </xsl:text>
       
   846     <xsl:text>            try {
       
   847 </xsl:text>
       
   848     <xsl:text>                init.call(widget);
       
   849 </xsl:text>
       
   850     <xsl:text>            } catch(err) {
       
   851 </xsl:text>
       
   852     <xsl:text>                console.log(err);
       
   853 </xsl:text>
       
   854     <xsl:text>            }
       
   855 </xsl:text>
       
   856     <xsl:text>        }
       
   857 </xsl:text>
       
   858     <xsl:text>    });
       
   859 </xsl:text>
       
   860     <xsl:text>};
       
   861 </xsl:text>
       
   862     <xsl:text>
       
   863 </xsl:text>
       
   864     <xsl:text>// Open WebSocket to relative "/ws" address
       
   865 </xsl:text>
       
   866     <xsl:text>var ws = new WebSocket(window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws'));
       
   867 </xsl:text>
       
   868     <xsl:text>ws.binaryType = 'arraybuffer';
       
   869 </xsl:text>
       
   870     <xsl:text>
       
   871 </xsl:text>
       
   872     <xsl:text>const dvgetters = {
       
   873 </xsl:text>
       
   874     <xsl:text>    INT: (dv,offset) =&gt; [dv.getInt16(offset, true), 2],
       
   875 </xsl:text>
       
   876     <xsl:text>    BOOL: (dv,offset) =&gt; [dv.getInt8(offset, true), 1],
       
   877 </xsl:text>
       
   878     <xsl:text>    STRING: (dv, offset) =&gt; {
       
   879 </xsl:text>
       
   880     <xsl:text>        size = dv.getInt8(offset);
       
   881 </xsl:text>
       
   882     <xsl:text>        return [
       
   883 </xsl:text>
       
   884     <xsl:text>            String.fromCharCode.apply(null, new Uint8Array(
       
   885 </xsl:text>
       
   886     <xsl:text>                dv.buffer, /* original buffer */
       
   887 </xsl:text>
       
   888     <xsl:text>                offset + 1, /* string starts after size*/
       
   889 </xsl:text>
       
   890     <xsl:text>                size /* size of string */
       
   891 </xsl:text>
       
   892     <xsl:text>            )), size + 1]; /* total increment */
       
   893 </xsl:text>
       
   894     <xsl:text>    }
       
   895 </xsl:text>
       
   896     <xsl:text>};
       
   897 </xsl:text>
       
   898     <xsl:text>
       
   899 </xsl:text>
       
   900     <xsl:text>// Apply updates recieved through ws.onmessage to subscribed widgets
       
   901 </xsl:text>
       
   902     <xsl:text>function apply_updates() {
       
   903 </xsl:text>
       
   904     <xsl:text>    for(let index in updates){
       
   905 </xsl:text>
       
   906     <xsl:text>        // serving as a key, index becomes a string
       
   907 </xsl:text>
       
   908     <xsl:text>        // -&gt; pass Number(index) instead
       
   909 </xsl:text>
       
   910     <xsl:text>        dispatch_value(Number(index), updates[index]);
       
   911 </xsl:text>
       
   912     <xsl:text>        delete updates[index];
       
   913 </xsl:text>
       
   914     <xsl:text>    }
       
   915 </xsl:text>
       
   916     <xsl:text>}
       
   917 </xsl:text>
       
   918     <xsl:text>
       
   919 </xsl:text>
       
   920     <xsl:text>// Called on requestAnimationFrame, modifies DOM
       
   921 </xsl:text>
       
   922     <xsl:text>var requestAnimationFrameID = null;
       
   923 </xsl:text>
       
   924     <xsl:text>function animate() {
       
   925 </xsl:text>
       
   926     <xsl:text>    // Do the page swith if any one pending
       
   927 </xsl:text>
       
   928     <xsl:text>    if(current_subscribed_page != current_visible_page){
       
   929 </xsl:text>
       
   930     <xsl:text>        switch_visible_page(current_subscribed_page);
       
   931 </xsl:text>
       
   932     <xsl:text>    }
       
   933 </xsl:text>
       
   934     <xsl:text>    apply_updates();
       
   935 </xsl:text>
       
   936     <xsl:text>    requestAnimationFrameID = null;
       
   937 </xsl:text>
       
   938     <xsl:text>}
       
   939 </xsl:text>
       
   940     <xsl:text>
       
   941 </xsl:text>
       
   942     <xsl:text>function requestHMIAnimation() {
       
   943 </xsl:text>
       
   944     <xsl:text>    if(requestAnimationFrameID == null){
       
   945 </xsl:text>
       
   946     <xsl:text>        requestAnimationFrameID = window.requestAnimationFrame(animate);
       
   947 </xsl:text>
       
   948     <xsl:text>    }
       
   949 </xsl:text>
       
   950     <xsl:text>}
       
   951 </xsl:text>
       
   952     <xsl:text>
       
   953 </xsl:text>
       
   954     <xsl:text>// Message reception handler
       
   955 </xsl:text>
       
   956     <xsl:text>// Hash is verified and HMI values updates resulting from binary parsing
       
   957 </xsl:text>
       
   958     <xsl:text>// are stored until browser can compute next frame, DOM is left untouched
       
   959 </xsl:text>
       
   960     <xsl:text>ws.onmessage = function (evt) {
       
   961 </xsl:text>
       
   962     <xsl:text>
       
   963 </xsl:text>
       
   964     <xsl:text>    let data = evt.data;
       
   965 </xsl:text>
       
   966     <xsl:text>    let dv = new DataView(data);
       
   967 </xsl:text>
       
   968     <xsl:text>    let i = 0;
       
   969 </xsl:text>
       
   970     <xsl:text>    try {
       
   971 </xsl:text>
       
   972     <xsl:text>        for(let hash_int of hmi_hash) {
       
   973 </xsl:text>
       
   974     <xsl:text>            if(hash_int != dv.getUint8(i)){
       
   975 </xsl:text>
       
   976     <xsl:text>                throw new Error("Hash doesn't match");
       
   977 </xsl:text>
       
   978     <xsl:text>            };
       
   979 </xsl:text>
       
   980     <xsl:text>            i++;
       
   981 </xsl:text>
       
   982     <xsl:text>        };
       
   983 </xsl:text>
       
   984     <xsl:text>
       
   985 </xsl:text>
       
   986     <xsl:text>        while(i &lt; data.byteLength){
       
   987 </xsl:text>
       
   988     <xsl:text>            let index = dv.getUint32(i, true);
       
   989 </xsl:text>
       
   990     <xsl:text>            i += 4;
       
   991 </xsl:text>
       
   992     <xsl:text>            let iectype = hmitree_types[index];
       
   993 </xsl:text>
       
   994     <xsl:text>            if(iectype != undefined){
       
   995 </xsl:text>
       
   996     <xsl:text>                let dvgetter = dvgetters[iectype];
       
   997 </xsl:text>
       
   998     <xsl:text>                let [value, bytesize] = dvgetter(dv,i);
       
   999 </xsl:text>
       
  1000     <xsl:text>                updates[index] = value;
       
  1001 </xsl:text>
       
  1002     <xsl:text>                i += bytesize;
       
  1003 </xsl:text>
       
  1004     <xsl:text>            } else {
       
  1005 </xsl:text>
       
  1006     <xsl:text>                throw new Error("Unknown index "+index);
       
  1007 </xsl:text>
       
  1008     <xsl:text>            }
       
  1009 </xsl:text>
       
  1010     <xsl:text>        };
       
  1011 </xsl:text>
       
  1012     <xsl:text>        // register for rendering on next frame, since there are updates
       
  1013 </xsl:text>
       
  1014     <xsl:text>        requestHMIAnimation();
       
  1015 </xsl:text>
       
  1016     <xsl:text>    } catch(err) {
       
  1017 </xsl:text>
       
  1018     <xsl:text>        // 1003 is for "Unsupported Data"
       
  1019 </xsl:text>
       
  1020     <xsl:text>        // ws.close(1003, err.message);
       
  1021 </xsl:text>
       
  1022     <xsl:text>
       
  1023 </xsl:text>
       
  1024     <xsl:text>        // TODO : remove debug alert ?
       
  1025 </xsl:text>
       
  1026     <xsl:text>        alert("Error : "+err.message+"\nHMI will be reloaded.");
       
  1027 </xsl:text>
       
  1028     <xsl:text>
       
  1029 </xsl:text>
       
  1030     <xsl:text>        // force reload ignoring cache
       
  1031 </xsl:text>
       
  1032     <xsl:text>        location.reload(true);
       
  1033 </xsl:text>
       
  1034     <xsl:text>    }
       
  1035 </xsl:text>
       
  1036     <xsl:text>};
       
  1037 </xsl:text>
       
  1038     <xsl:text>
       
  1039 </xsl:text>
       
  1040     <xsl:text>
       
  1041 </xsl:text>
       
  1042     <xsl:text>function send_blob(data) {
       
  1043 </xsl:text>
       
  1044     <xsl:text>    if(data.length &gt; 0) {
       
  1045 </xsl:text>
       
  1046     <xsl:text>        ws.send(new Blob([new Uint8Array(hmi_hash)].concat(data)));
       
  1047 </xsl:text>
       
  1048     <xsl:text>    };
       
  1049 </xsl:text>
       
  1050     <xsl:text>};
       
  1051 </xsl:text>
       
  1052     <xsl:text>
       
  1053 </xsl:text>
       
  1054     <xsl:text>const typedarray_types = {
       
  1055 </xsl:text>
       
  1056     <xsl:text>    INT: (number) =&gt; new Int16Array([number]),
       
  1057 </xsl:text>
       
  1058     <xsl:text>    BOOL: (truth) =&gt; new Int16Array([truth]),
       
  1059 </xsl:text>
       
  1060     <xsl:text>    STRING: (str) =&gt; {
       
  1061 </xsl:text>
       
  1062     <xsl:text>        // beremiz default string max size is 128
       
  1063 </xsl:text>
       
  1064     <xsl:text>        str = str.slice(0,128);
       
  1065 </xsl:text>
       
  1066     <xsl:text>        binary = new Uint8Array(str.length + 1);
       
  1067 </xsl:text>
       
  1068     <xsl:text>        binary[0] = str.length;
       
  1069 </xsl:text>
       
  1070     <xsl:text>        for(var i = 0; i &lt; str.length; i++){
       
  1071 </xsl:text>
       
  1072     <xsl:text>            binary[i+1] = str.charCodeAt(i);
       
  1073 </xsl:text>
       
  1074     <xsl:text>        }
       
  1075 </xsl:text>
       
  1076     <xsl:text>        return binary;
       
  1077 </xsl:text>
       
  1078     <xsl:text>    }
       
  1079 </xsl:text>
       
  1080     <xsl:text>    /* TODO */
       
  1081 </xsl:text>
       
  1082     <xsl:text>};
       
  1083 </xsl:text>
       
  1084     <xsl:text>
       
  1085 </xsl:text>
       
  1086     <xsl:text>function send_reset() {
       
  1087 </xsl:text>
       
  1088     <xsl:text>    send_blob(new Uint8Array([1])); /* reset = 1 */
       
  1089 </xsl:text>
       
  1090     <xsl:text>};
       
  1091 </xsl:text>
       
  1092     <xsl:text>
       
  1093 </xsl:text>
       
  1094     <xsl:text>// subscription state, as it should be in hmi server
       
  1095 </xsl:text>
       
  1096     <xsl:text>// hmitree indexed array of integers
       
  1097 </xsl:text>
       
  1098     <xsl:text>var subscriptions =  hmitree_types.map(_ignored =&gt; 0);
       
  1099 </xsl:text>
       
  1100     <xsl:text>
       
  1101 </xsl:text>
       
  1102     <xsl:text>// subscription state as needed by widget now
       
  1103 </xsl:text>
       
  1104     <xsl:text>// hmitree indexed array of Sets of widgets objects
       
  1105 </xsl:text>
       
  1106     <xsl:text>var subscribers = hmitree_types.map(_ignored =&gt; new Set());
       
  1107 </xsl:text>
       
  1108     <xsl:text>
       
  1109 </xsl:text>
       
  1110     <xsl:text>// artificially subscribe the watchdog widget to "/heartbeat" hmi variable
       
  1111 </xsl:text>
       
  1112     <xsl:text>// Since dispatch directly calls change_hmi_value,
       
  1113 </xsl:text>
       
  1114     <xsl:text>// PLC will periodically send variable at given frequency
       
  1115 </xsl:text>
       
  1116     <xsl:text>subscribers[heartbeat_index].add({
       
  1117 </xsl:text>
       
  1118     <xsl:text>    /* type: "Watchdog", */
       
  1119 </xsl:text>
       
  1120     <xsl:text>    frequency: 1,
       
  1121 </xsl:text>
       
  1122     <xsl:text>    indexes: [heartbeat_index],
       
  1123 </xsl:text>
       
  1124     <xsl:text>    dispatch: function(value) {
       
  1125 </xsl:text>
       
  1126     <xsl:text>        // console.log("Heartbeat" + value);
       
  1127 </xsl:text>
       
  1128     <xsl:text>        change_hmi_value(heartbeat_index, "+1");
       
  1129 </xsl:text>
       
  1130     <xsl:text>    }
       
  1131 </xsl:text>
       
  1132     <xsl:text>});
       
  1133 </xsl:text>
       
  1134     <xsl:text>
       
  1135 </xsl:text>
       
  1136     <xsl:text>function update_subscriptions() {
       
  1137 </xsl:text>
       
  1138     <xsl:text>    let delta = [];
       
  1139 </xsl:text>
       
  1140     <xsl:text>    for(let index = 0; index &lt; subscribers.length; index++){
       
  1141 </xsl:text>
       
  1142     <xsl:text>        let widgets = subscribers[index];
       
  1143 </xsl:text>
       
  1144     <xsl:text>
       
  1145 </xsl:text>
       
  1146     <xsl:text>        // periods are in ms
       
  1147 </xsl:text>
       
  1148     <xsl:text>        let previous_period = subscriptions[index];
       
  1149 </xsl:text>
       
  1150     <xsl:text>
       
  1151 </xsl:text>
       
  1152     <xsl:text>        // subscribing with a zero period is unsubscribing
       
  1153 </xsl:text>
       
  1154     <xsl:text>        let new_period = 0;
       
  1155 </xsl:text>
       
  1156     <xsl:text>        if(widgets.size &gt; 0) {
       
  1157 </xsl:text>
       
  1158     <xsl:text>            let maxfreq = 0;
       
  1159 </xsl:text>
       
  1160     <xsl:text>            for(let widget of widgets)
       
  1161 </xsl:text>
       
  1162     <xsl:text>                if(maxfreq &lt; widget.frequency)
       
  1163 </xsl:text>
       
  1164     <xsl:text>                    maxfreq = widget.frequency;
       
  1165 </xsl:text>
       
  1166     <xsl:text>
       
  1167 </xsl:text>
       
  1168     <xsl:text>            if(maxfreq != 0)
       
  1169 </xsl:text>
       
  1170     <xsl:text>                new_period = 1000/maxfreq;
       
  1171 </xsl:text>
       
  1172     <xsl:text>        }
       
  1173 </xsl:text>
       
  1174     <xsl:text>
       
  1175 </xsl:text>
       
  1176     <xsl:text>        if(previous_period != new_period) {
       
  1177 </xsl:text>
       
  1178     <xsl:text>            subscriptions[index] = new_period;
       
  1179 </xsl:text>
       
  1180     <xsl:text>            delta.push(
       
  1181 </xsl:text>
       
  1182     <xsl:text>                new Uint8Array([2]), /* subscribe = 2 */
       
  1183 </xsl:text>
       
  1184     <xsl:text>                new Uint32Array([index]),
       
  1185 </xsl:text>
       
  1186     <xsl:text>                new Uint16Array([new_period]));
       
  1187 </xsl:text>
       
  1188     <xsl:text>        }
       
  1189 </xsl:text>
       
  1190     <xsl:text>    }
       
  1191 </xsl:text>
       
  1192     <xsl:text>    send_blob(delta);
       
  1193 </xsl:text>
       
  1194     <xsl:text>};
       
  1195 </xsl:text>
       
  1196     <xsl:text>
       
  1197 </xsl:text>
       
  1198     <xsl:text>function send_hmi_value(index, value) {
       
  1199 </xsl:text>
       
  1200     <xsl:text>    let iectype = hmitree_types[index];
       
  1201 </xsl:text>
       
  1202     <xsl:text>    let tobinary = typedarray_types[iectype];
       
  1203 </xsl:text>
       
  1204     <xsl:text>    send_blob([
       
  1205 </xsl:text>
       
  1206     <xsl:text>        new Uint8Array([0]),  /* setval = 0 */
       
  1207 </xsl:text>
       
  1208     <xsl:text>        new Uint32Array([index]),
       
  1209 </xsl:text>
       
  1210     <xsl:text>        tobinary(value)]);
       
  1211 </xsl:text>
       
  1212     <xsl:text>
       
  1213 </xsl:text>
       
  1214     <xsl:text>    cache[index] = value;
       
  1215 </xsl:text>
       
  1216     <xsl:text>};
       
  1217 </xsl:text>
       
  1218     <xsl:text>
       
  1219 </xsl:text>
       
  1220     <xsl:text>function change_hmi_value(index, opstr) {
       
  1221 </xsl:text>
       
  1222     <xsl:text>    let op = opstr[0];
       
  1223 </xsl:text>
       
  1224     <xsl:text>    let given_val = opstr.slice(1);
       
  1225 </xsl:text>
       
  1226     <xsl:text>    let old_val = cache[index]
       
  1227 </xsl:text>
       
  1228     <xsl:text>    let new_val;
       
  1229 </xsl:text>
       
  1230     <xsl:text>    switch(op){
       
  1231 </xsl:text>
       
  1232     <xsl:text>      case "=":
       
  1233 </xsl:text>
       
  1234     <xsl:text>        eval("new_val"+opstr);
       
  1235 </xsl:text>
       
  1236     <xsl:text>        break;
       
  1237 </xsl:text>
       
  1238     <xsl:text>      case "+":
       
  1239 </xsl:text>
       
  1240     <xsl:text>      case "-":
       
  1241 </xsl:text>
       
  1242     <xsl:text>      case "*":
       
  1243 </xsl:text>
       
  1244     <xsl:text>      case "/":
       
  1245 </xsl:text>
       
  1246     <xsl:text>        if(old_val != undefined)
       
  1247 </xsl:text>
       
  1248     <xsl:text>            new_val = eval("old_val"+opstr);
       
  1249 </xsl:text>
       
  1250     <xsl:text>        break;
       
  1251 </xsl:text>
       
  1252     <xsl:text>    }
       
  1253 </xsl:text>
       
  1254     <xsl:text>    if(new_val != undefined &amp;&amp; old_val != new_val)
       
  1255 </xsl:text>
       
  1256     <xsl:text>        send_hmi_value(index, new_val);
       
  1257 </xsl:text>
       
  1258     <xsl:text>    return new_val;
       
  1259 </xsl:text>
       
  1260     <xsl:text>}
       
  1261 </xsl:text>
       
  1262     <xsl:text>
       
  1263 </xsl:text>
       
  1264     <xsl:text>var current_visible_page;
       
  1265 </xsl:text>
       
  1266     <xsl:text>var current_subscribed_page;
       
  1267 </xsl:text>
       
  1268     <xsl:text>
       
  1269 </xsl:text>
       
  1270     <xsl:text>function prepare_svg() {
       
  1271 </xsl:text>
       
  1272     <xsl:text>    for(let eltid in detachable_elements){
       
  1273 </xsl:text>
       
  1274     <xsl:text>        let [element,parent] = detachable_elements[eltid];
       
  1275 </xsl:text>
       
  1276     <xsl:text>        parent.removeChild(element);
       
  1277 </xsl:text>
       
  1278     <xsl:text>    }
       
  1279 </xsl:text>
       
  1280     <xsl:text>};
       
  1281 </xsl:text>
       
  1282     <xsl:text>
       
  1283 </xsl:text>
       
  1284     <xsl:text>function switch_page(page_name, page_index) {
       
  1285 </xsl:text>
       
  1286     <xsl:text>    if(current_subscribed_page != current_visible_page){
       
  1287 </xsl:text>
       
  1288     <xsl:text>        /* page switch already going */
       
  1289 </xsl:text>
       
  1290     <xsl:text>        /* TODO LOG ERROR */
       
  1291 </xsl:text>
       
  1292     <xsl:text>        return;
       
  1293 </xsl:text>
       
  1294     <xsl:text>    } else if(page_name == current_visible_page){
       
  1295 </xsl:text>
       
  1296     <xsl:text>        /* already in that page */
       
  1297 </xsl:text>
       
  1298     <xsl:text>        /* TODO LOG ERROR */
       
  1299 </xsl:text>
       
  1300     <xsl:text>        return;
       
  1301 </xsl:text>
       
  1302     <xsl:text>    }
       
  1303 </xsl:text>
       
  1304     <xsl:text>    switch_subscribed_page(page_name, page_index);
       
  1305 </xsl:text>
       
  1306     <xsl:text>};
       
  1307 </xsl:text>
       
  1308     <xsl:text>
       
  1309 </xsl:text>
       
  1310     <xsl:text>function* chain(a,b){
       
  1311 </xsl:text>
       
  1312     <xsl:text>    yield* a;
       
  1313 </xsl:text>
       
  1314     <xsl:text>    yield* b;
       
  1315 </xsl:text>
       
  1316     <xsl:text>};
       
  1317 </xsl:text>
       
  1318     <xsl:text>
       
  1319 </xsl:text>
       
  1320     <xsl:text>function switch_subscribed_page(page_name, page_index) {
       
  1321 </xsl:text>
       
  1322     <xsl:text>    let old_desc = page_desc[current_subscribed_page];
       
  1323 </xsl:text>
       
  1324     <xsl:text>    let new_desc = page_desc[page_name];
       
  1325 </xsl:text>
       
  1326     <xsl:text>
       
  1327 </xsl:text>
       
  1328     <xsl:text>    if(new_desc == undefined){
       
  1329 </xsl:text>
       
  1330     <xsl:text>        /* TODO LOG ERROR */
       
  1331 </xsl:text>
       
  1332     <xsl:text>        return;
       
  1333 </xsl:text>
       
  1334     <xsl:text>    }
       
  1335 </xsl:text>
       
  1336     <xsl:text>
       
  1337 </xsl:text>
       
  1338     <xsl:text>    if(page_index == undefined){
       
  1339 </xsl:text>
       
  1340     <xsl:text>        page_index = new_desc.page_index;
       
  1341 </xsl:text>
       
  1342     <xsl:text>    }
       
  1343 </xsl:text>
       
  1344     <xsl:text>
       
  1345 </xsl:text>
       
  1346     <xsl:text>    if(old_desc){
       
  1347 </xsl:text>
       
  1348     <xsl:text>        for(let widget of old_desc.absolute_widgets){
       
  1349 </xsl:text>
       
  1350     <xsl:text>            /* remove subsribers */
       
  1351 </xsl:text>
       
  1352     <xsl:text>            for(let index of widget.indexes){
       
  1353 </xsl:text>
       
  1354     <xsl:text>                subscribers[index].delete(widget);
       
  1355 </xsl:text>
       
  1356     <xsl:text>            }
       
  1357 </xsl:text>
       
  1358     <xsl:text>        }
       
  1359 </xsl:text>
       
  1360     <xsl:text>        for(let widget of old_desc.relative_widgets){
       
  1361 </xsl:text>
       
  1362     <xsl:text>            /* remove subsribers */
       
  1363 </xsl:text>
       
  1364     <xsl:text>            for(let index of widget.indexes){
       
  1365 </xsl:text>
       
  1366     <xsl:text>                let idx = widget.offset ? index + widget.offset : index;
       
  1367 </xsl:text>
       
  1368     <xsl:text>                subscribers[idx].delete(widget);
       
  1369 </xsl:text>
       
  1370     <xsl:text>            }
       
  1371 </xsl:text>
       
  1372     <xsl:text>            /* lose the offset */
       
  1373 </xsl:text>
       
  1374     <xsl:text>            delete widget.offset;
       
  1375 </xsl:text>
       
  1376     <xsl:text>        }
       
  1377 </xsl:text>
       
  1378     <xsl:text>    }
       
  1379 </xsl:text>
       
  1380     <xsl:text>    for(let widget of new_desc.absolute_widgets){
       
  1381 </xsl:text>
       
  1382     <xsl:text>        /* add widget's subsribers */
       
  1383 </xsl:text>
       
  1384     <xsl:text>        for(let index of widget.indexes){
       
  1385 </xsl:text>
       
  1386     <xsl:text>            subscribers[index].add(widget);
       
  1387 </xsl:text>
       
  1388     <xsl:text>        }
       
  1389 </xsl:text>
       
  1390     <xsl:text>    }
       
  1391 </xsl:text>
       
  1392     <xsl:text>    var new_offset = page_index == undefined ? 0 : page_index - new_desc.page_index;
       
  1393 </xsl:text>
       
  1394     <xsl:text>    for(let widget of new_desc.relative_widgets){
       
  1395 </xsl:text>
       
  1396     <xsl:text>        /* set the offset because relative */
       
  1397 </xsl:text>
       
  1398     <xsl:text>        widget.offset = new_offset;
       
  1399 </xsl:text>
       
  1400     <xsl:text>        /* add widget's subsribers */
       
  1401 </xsl:text>
       
  1402     <xsl:text>        for(let index of widget.indexes){
       
  1403 </xsl:text>
       
  1404     <xsl:text>            subscribers[index + new_offset].add(widget);
       
  1405 </xsl:text>
       
  1406     <xsl:text>        }
       
  1407 </xsl:text>
       
  1408     <xsl:text>    }
       
  1409 </xsl:text>
       
  1410     <xsl:text>
       
  1411 </xsl:text>
       
  1412     <xsl:text>    update_subscriptions();
       
  1413 </xsl:text>
       
  1414     <xsl:text>
       
  1415 </xsl:text>
       
  1416     <xsl:text>    current_subscribed_page = page_name;
       
  1417 </xsl:text>
       
  1418     <xsl:text>
       
  1419 </xsl:text>
       
  1420     <xsl:text>    requestHMIAnimation();
       
  1421 </xsl:text>
       
  1422     <xsl:text>}
       
  1423 </xsl:text>
       
  1424     <xsl:text>
       
  1425 </xsl:text>
       
  1426     <xsl:text>function switch_visible_page(page_name) {
       
  1427 </xsl:text>
       
  1428     <xsl:text>
       
  1429 </xsl:text>
       
  1430     <xsl:text>    let old_desc = page_desc[current_visible_page];
       
  1431 </xsl:text>
       
  1432     <xsl:text>    let new_desc = page_desc[page_name];
       
  1433 </xsl:text>
       
  1434     <xsl:text>
       
  1435 </xsl:text>
       
  1436     <xsl:text>    if(old_desc){
       
  1437 </xsl:text>
       
  1438     <xsl:text>        for(let eltid in old_desc.required_detachables){
       
  1439 </xsl:text>
       
  1440     <xsl:text>            if(!(eltid in new_desc.required_detachables)){
       
  1441 </xsl:text>
       
  1442     <xsl:text>                let [element, parent] = old_desc.required_detachables[eltid];
       
  1443 </xsl:text>
       
  1444     <xsl:text>                parent.removeChild(element);
       
  1445 </xsl:text>
       
  1446     <xsl:text>            }
       
  1447 </xsl:text>
       
  1448     <xsl:text>        }
       
  1449 </xsl:text>
       
  1450     <xsl:text>        for(let eltid in new_desc.required_detachables){
       
  1451 </xsl:text>
       
  1452     <xsl:text>            if(!(eltid in old_desc.required_detachables)){
       
  1453 </xsl:text>
       
  1454     <xsl:text>                let [element, parent] = new_desc.required_detachables[eltid];
       
  1455 </xsl:text>
       
  1456     <xsl:text>                parent.appendChild(element);
       
  1457 </xsl:text>
       
  1458     <xsl:text>            }
       
  1459 </xsl:text>
       
  1460     <xsl:text>        }
       
  1461 </xsl:text>
       
  1462     <xsl:text>    }else{
       
  1463 </xsl:text>
       
  1464     <xsl:text>        for(let eltid in new_desc.required_detachables){
       
  1465 </xsl:text>
       
  1466     <xsl:text>            let [element, parent] = new_desc.required_detachables[eltid];
       
  1467 </xsl:text>
       
  1468     <xsl:text>            parent.appendChild(element);
       
  1469 </xsl:text>
       
  1470     <xsl:text>        }
       
  1471 </xsl:text>
       
  1472     <xsl:text>    }
       
  1473 </xsl:text>
       
  1474     <xsl:text>
       
  1475 </xsl:text>
       
  1476     <xsl:text>    for(let widget of chain(new_desc.absolute_widgets,new_desc.relative_widgets)){
       
  1477 </xsl:text>
       
  1478     <xsl:text>        for(let index of widget.indexes){
       
  1479 </xsl:text>
       
  1480     <xsl:text>            /* dispatch current cache in newly opened page widgets */
       
  1481 </xsl:text>
       
  1482     <xsl:text>            let cached_val = cache[index];
       
  1483 </xsl:text>
       
  1484     <xsl:text>            if(cached_val != undefined)
       
  1485 </xsl:text>
       
  1486     <xsl:text>                dispatch_value_to_widget(widget, index, cached_val, cached_val);
       
  1487 </xsl:text>
       
  1488     <xsl:text>        }
       
  1489 </xsl:text>
       
  1490     <xsl:text>    }
       
  1491 </xsl:text>
       
  1492     <xsl:text>
       
  1493 </xsl:text>
       
  1494     <xsl:text>    svg_root.setAttribute('viewBox',new_desc.bbox.join(" "));
       
  1495 </xsl:text>
       
  1496     <xsl:text>    current_visible_page = page_name;
       
  1497 </xsl:text>
       
  1498     <xsl:text>};
       
  1499 </xsl:text>
       
  1500     <xsl:text>
       
  1501 </xsl:text>
       
  1502     <xsl:text>
       
  1503 </xsl:text>
       
  1504     <xsl:text>// Once connection established
       
  1505 </xsl:text>
       
  1506     <xsl:text>ws.onopen = function (evt) {
       
  1507 </xsl:text>
       
  1508     <xsl:text>    init_widgets();
       
  1509 </xsl:text>
       
  1510     <xsl:text>    send_reset();
       
  1511 </xsl:text>
       
  1512     <xsl:text>    // show main page
       
  1513 </xsl:text>
       
  1514     <xsl:text>    prepare_svg();
       
  1515 </xsl:text>
       
  1516     <xsl:text>    switch_page(default_page);
       
  1517 </xsl:text>
       
  1518     <xsl:text>};
       
  1519 </xsl:text>
       
  1520     <xsl:text>
       
  1521 </xsl:text>
       
  1522     <xsl:text>ws.onclose = function (evt) {
       
  1523 </xsl:text>
       
  1524     <xsl:text>    // TODO : add visible notification while waiting for reload
       
  1525 </xsl:text>
       
  1526     <xsl:text>    console.log("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+" Reload in 10s.");
       
  1527 </xsl:text>
       
  1528     <xsl:text>    // TODO : re-enable auto reload when not in debug
       
  1529 </xsl:text>
       
  1530     <xsl:text>    //window.setTimeout(() =&gt; location.reload(true), 10000);
       
  1531 </xsl:text>
       
  1532     <xsl:text>    alert("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+".");
       
  1533 </xsl:text>
       
  1534     <xsl:text>
       
  1535 </xsl:text>
       
  1536     <xsl:text>};
       
  1537 </xsl:text>
       
  1538     <xsl:text>//})();
       
  1539 </xsl:text>
  1539   </xsl:template>
  1540   </xsl:template>
  1540   <xsl:template mode="widget_defs" match="widget[@type='Display']">
  1541   <xsl:template mode="widget_defs" match="widget[@type='Display']">
  1541     <xsl:param name="hmi_element"/>
  1542     <xsl:param name="hmi_element"/>
  1542     <xsl:text>    frequency: 5,
  1543     <xsl:text>    frequency: 5,
  1543 </xsl:text>
  1544 </xsl:text>