svghmi/gen_index_xhtml.xslt
branchsvghmi
changeset 2883 8e3d130399b0
parent 2881 3bb49f93d48c
child 2885 f398896b7ebf
equal deleted inserted replaced
2882:ac08a5d15c15 2883:8e3d130399b0
   622 </xsl:text>
   622 </xsl:text>
   623         </xsl:otherwise>
   623         </xsl:otherwise>
   624       </xsl:choose>
   624       </xsl:choose>
   625     </xsl:for-each>
   625     </xsl:for-each>
   626   </xsl:template>
   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>
       
  1540   </xsl:template>
       
  1541   <xsl:template mode="widget_defs" match="widget[@type='Display']">
       
  1542     <xsl:param name="hmi_element"/>
       
  1543     <xsl:text>    frequency: 5,
       
  1544 </xsl:text>
       
  1545     <xsl:text>    dispatch: function(value) {
       
  1546 </xsl:text>
       
  1547     <xsl:choose>
       
  1548       <xsl:when test="$hmi_element[self::svg:text]">
       
  1549         <xsl:text>      this.element.textContent = String(value);
       
  1550 </xsl:text>
       
  1551       </xsl:when>
       
  1552       <xsl:otherwise>
       
  1553         <xsl:message terminate="no">
       
  1554           <xsl:text>Display widget as a group not implemented</xsl:text>
       
  1555         </xsl:message>
       
  1556       </xsl:otherwise>
       
  1557     </xsl:choose>
       
  1558     <xsl:text>    },
       
  1559 </xsl:text>
       
  1560   </xsl:template>
       
  1561   <xsl:template mode="widget_defs" match="widget[@type='Meter']">
       
  1562     <xsl:param name="hmi_element"/>
       
  1563     <xsl:text>    frequency: 10,
       
  1564 </xsl:text>
       
  1565     <xsl:call-template name="defs_by_labels">
       
  1566       <xsl:with-param name="hmi_element" select="$hmi_element"/>
       
  1567       <xsl:with-param name="labels">
       
  1568         <xsl:text>needle range</xsl:text>
       
  1569       </xsl:with-param>
       
  1570     </xsl:call-template>
       
  1571     <xsl:call-template name="defs_by_labels">
       
  1572       <xsl:with-param name="hmi_element" select="$hmi_element"/>
       
  1573       <xsl:with-param name="labels">
       
  1574         <xsl:text>value min max</xsl:text>
       
  1575       </xsl:with-param>
       
  1576       <xsl:with-param name="mandatory" select="'no'"/>
       
  1577     </xsl:call-template>
       
  1578     <xsl:text>    dispatch: function(value) {
       
  1579 </xsl:text>
       
  1580     <xsl:text>        if(this.value_elt)
       
  1581 </xsl:text>
       
  1582     <xsl:text>            this.value_elt.textContent = String(value);
       
  1583 </xsl:text>
       
  1584     <xsl:text>        let [min,max,totallength] = this.range;
       
  1585 </xsl:text>
       
  1586     <xsl:text>        let length = Math.max(0,Math.min(totallength,(Number(value)-min)*totallength/(max-min)));
       
  1587 </xsl:text>
       
  1588     <xsl:text>        let tip = this.range_elt.getPointAtLength(length);
       
  1589 </xsl:text>
       
  1590     <xsl:text>        this.needle_elt.setAttribute('d', "M "+this.origin.x+","+this.origin.y+" "+tip.x+","+tip.y);
       
  1591 </xsl:text>
       
  1592     <xsl:text>    },
       
  1593 </xsl:text>
       
  1594     <xsl:text>    origin: undefined,
       
  1595 </xsl:text>
       
  1596     <xsl:text>    range: undefined,
       
  1597 </xsl:text>
       
  1598     <xsl:text>    init: function() {
       
  1599 </xsl:text>
       
  1600     <xsl:text>        let min = this.min_elt ?
       
  1601 </xsl:text>
       
  1602     <xsl:text>                    Number(this.min_elt.textContent) :
       
  1603 </xsl:text>
       
  1604     <xsl:text>                    this.args.length &gt;= 1 ? this.args[0] : 0;
       
  1605 </xsl:text>
       
  1606     <xsl:text>        let max = this.max_elt ?
       
  1607 </xsl:text>
       
  1608     <xsl:text>                    Number(this.max_elt.textContent) :
       
  1609 </xsl:text>
       
  1610     <xsl:text>                    this.args.length &gt;= 2 ? this.args[1] : 100;
       
  1611 </xsl:text>
       
  1612     <xsl:text>        this.range = [min, max, this.range_elt.getTotalLength()]
       
  1613 </xsl:text>
       
  1614     <xsl:text>        this.origin = this.needle_elt.getPointAtLength(0);
       
  1615 </xsl:text>
       
  1616     <xsl:text>    },
       
  1617 </xsl:text>
       
  1618   </xsl:template>
       
  1619   <func:function name="func:escape_quotes">
   627   <func:function name="func:escape_quotes">
  1620     <xsl:param name="txt"/>
   628     <xsl:param name="txt"/>
  1621     <xsl:variable name="frst" select="substring-before($txt,'&quot;')"/>
   629     <xsl:variable name="frst" select="substring-before($txt,'&quot;')"/>
  1622     <xsl:variable name="frstln" select="string-length($frst)"/>
   630     <xsl:variable name="frstln" select="string-length($frst)"/>
  1623     <xsl:choose>
   631     <xsl:choose>
  1627       <xsl:otherwise>
   635       <xsl:otherwise>
  1628         <func:result select="$txt"/>
   636         <func:result select="$txt"/>
  1629       </xsl:otherwise>
   637       </xsl:otherwise>
  1630     </xsl:choose>
   638     </xsl:choose>
  1631   </func:function>
   639   </func:function>
       
   640   <xsl:template mode="widget_defs" match="widget[@type='Display']">
       
   641     <xsl:param name="hmi_element"/>
       
   642     <xsl:text>    frequency: 5,
       
   643 </xsl:text>
       
   644     <xsl:text>    dispatch: function(value) {
       
   645 </xsl:text>
       
   646     <xsl:choose>
       
   647       <xsl:when test="$hmi_element[self::svg:text]">
       
   648         <xsl:text>      this.element.textContent = String(value);
       
   649 </xsl:text>
       
   650       </xsl:when>
       
   651       <xsl:otherwise>
       
   652         <xsl:message terminate="no">
       
   653           <xsl:text>Display widget as a group not implemented</xsl:text>
       
   654         </xsl:message>
       
   655       </xsl:otherwise>
       
   656     </xsl:choose>
       
   657     <xsl:text>    },
       
   658 </xsl:text>
       
   659   </xsl:template>
  1632   <xsl:template mode="widget_defs" match="widget[@type='Input']">
   660   <xsl:template mode="widget_defs" match="widget[@type='Input']">
  1633     <xsl:param name="hmi_element"/>
   661     <xsl:param name="hmi_element"/>
  1634     <xsl:variable name="value_elt">
   662     <xsl:variable name="value_elt">
  1635       <xsl:call-template name="defs_by_labels">
   663       <xsl:call-template name="defs_by_labels">
  1636         <xsl:with-param name="hmi_element" select="$hmi_element"/>
   664         <xsl:with-param name="hmi_element" select="$hmi_element"/>
  1686 </xsl:text>
   714 </xsl:text>
  1687     </xsl:for-each>
   715     </xsl:for-each>
  1688     <xsl:text>    },
   716     <xsl:text>    },
  1689 </xsl:text>
   717 </xsl:text>
  1690   </xsl:template>
   718   </xsl:template>
  1691   <xsl:template mode="widget_defs" match="widget[@type='Button']"/>
   719   <xsl:template mode="widget_defs" match="widget[@type='Jump']">
  1692   <xsl:template mode="widget_defs" match="widget[@type='Toggle']">
   720     <xsl:param name="hmi_element"/>
  1693     <xsl:text>    frequency: 5,
   721     <xsl:text>    on_click: function(evt) {
       
   722 </xsl:text>
       
   723     <xsl:text>        switch_page(this.args[0], this.indexes[0]);
       
   724 </xsl:text>
       
   725     <xsl:text>    },
       
   726 </xsl:text>
       
   727     <xsl:text>    init: function() {
       
   728 </xsl:text>
       
   729     <xsl:text>        this.element.setAttribute("onclick", "hmi_widgets['</xsl:text>
       
   730     <xsl:value-of select="$hmi_element/@id"/>
       
   731     <xsl:text>'].on_click(evt)");
       
   732 </xsl:text>
       
   733     <xsl:text>    },
       
   734 </xsl:text>
       
   735   </xsl:template>
       
   736   <xsl:template mode="widget_defs" match="widget[@type='Meter']">
       
   737     <xsl:param name="hmi_element"/>
       
   738     <xsl:text>    frequency: 10,
       
   739 </xsl:text>
       
   740     <xsl:call-template name="defs_by_labels">
       
   741       <xsl:with-param name="hmi_element" select="$hmi_element"/>
       
   742       <xsl:with-param name="labels">
       
   743         <xsl:text>needle range</xsl:text>
       
   744       </xsl:with-param>
       
   745     </xsl:call-template>
       
   746     <xsl:call-template name="defs_by_labels">
       
   747       <xsl:with-param name="hmi_element" select="$hmi_element"/>
       
   748       <xsl:with-param name="labels">
       
   749         <xsl:text>value min max</xsl:text>
       
   750       </xsl:with-param>
       
   751       <xsl:with-param name="mandatory" select="'no'"/>
       
   752     </xsl:call-template>
       
   753     <xsl:text>    dispatch: function(value) {
       
   754 </xsl:text>
       
   755     <xsl:text>        if(this.value_elt)
       
   756 </xsl:text>
       
   757     <xsl:text>            this.value_elt.textContent = String(value);
       
   758 </xsl:text>
       
   759     <xsl:text>        let [min,max,totallength] = this.range;
       
   760 </xsl:text>
       
   761     <xsl:text>        let length = Math.max(0,Math.min(totallength,(Number(value)-min)*totallength/(max-min)));
       
   762 </xsl:text>
       
   763     <xsl:text>        let tip = this.range_elt.getPointAtLength(length);
       
   764 </xsl:text>
       
   765     <xsl:text>        this.needle_elt.setAttribute('d', "M "+this.origin.x+","+this.origin.y+" "+tip.x+","+tip.y);
       
   766 </xsl:text>
       
   767     <xsl:text>    },
       
   768 </xsl:text>
       
   769     <xsl:text>    origin: undefined,
       
   770 </xsl:text>
       
   771     <xsl:text>    range: undefined,
       
   772 </xsl:text>
       
   773     <xsl:text>    init: function() {
       
   774 </xsl:text>
       
   775     <xsl:text>        let min = this.min_elt ?
       
   776 </xsl:text>
       
   777     <xsl:text>                    Number(this.min_elt.textContent) :
       
   778 </xsl:text>
       
   779     <xsl:text>                    this.args.length &gt;= 1 ? this.args[0] : 0;
       
   780 </xsl:text>
       
   781     <xsl:text>        let max = this.max_elt ?
       
   782 </xsl:text>
       
   783     <xsl:text>                    Number(this.max_elt.textContent) :
       
   784 </xsl:text>
       
   785     <xsl:text>                    this.args.length &gt;= 2 ? this.args[1] : 100;
       
   786 </xsl:text>
       
   787     <xsl:text>        this.range = [min, max, this.range_elt.getTotalLength()]
       
   788 </xsl:text>
       
   789     <xsl:text>        this.origin = this.needle_elt.getPointAtLength(0);
       
   790 </xsl:text>
       
   791     <xsl:text>    },
  1694 </xsl:text>
   792 </xsl:text>
  1695   </xsl:template>
   793   </xsl:template>
  1696   <xsl:template mode="widget_defs" match="widget[@type='Switch']">
   794   <xsl:template mode="widget_defs" match="widget[@type='Switch']">
  1697     <xsl:param name="hmi_element"/>
   795     <xsl:param name="hmi_element"/>
  1698     <xsl:text>    frequency: 5,
   796     <xsl:text>    frequency: 5,
  1748 </xsl:text>
   846 </xsl:text>
  1749     </xsl:for-each>
   847     </xsl:for-each>
  1750     <xsl:text>    ],
   848     <xsl:text>    ],
  1751 </xsl:text>
   849 </xsl:text>
  1752   </xsl:template>
   850   </xsl:template>
  1753   <xsl:template mode="widget_defs" match="widget[@type='Jump']">
   851   <xsl:template match="/">
  1754     <xsl:param name="hmi_element"/>
   852     <xsl:comment>
  1755     <xsl:text>    on_click: function(evt) {
   853       <xsl:text>Made with SVGHMI. https://beremiz.org</xsl:text>
  1756 </xsl:text>
   854     </xsl:comment>
  1757     <xsl:text>        switch_page(this.args[0], this.indexes[0]);
   855     <xsl:comment>
  1758 </xsl:text>
   856       <xsl:text>
  1759     <xsl:text>    },
   857 </xsl:text>
  1760 </xsl:text>
   858       <xsl:text>debug_hmitree:
  1761     <xsl:text>    init: function() {
   859 </xsl:text>
  1762 </xsl:text>
   860       <xsl:call-template name="debug_hmitree"/>
  1763     <xsl:text>        this.element.setAttribute("onclick", "hmi_widgets['</xsl:text>
   861       <xsl:text>
  1764     <xsl:value-of select="$hmi_element/@id"/>
   862 </xsl:text>
  1765     <xsl:text>'].on_click(evt)");
   863     </xsl:comment>
  1766 </xsl:text>
   864     <xsl:comment>
  1767     <xsl:text>    },
   865       <xsl:text>
       
   866 </xsl:text>
       
   867       <xsl:text>debug_geometry:
       
   868 </xsl:text>
       
   869       <xsl:call-template name="debug_geometry"/>
       
   870       <xsl:text>
       
   871 </xsl:text>
       
   872     </xsl:comment>
       
   873     <xsl:comment>
       
   874       <xsl:text>
       
   875 </xsl:text>
       
   876       <xsl:text>debug_detachables:
       
   877 </xsl:text>
       
   878       <xsl:call-template name="debug_detachables"/>
       
   879       <xsl:text>
       
   880 </xsl:text>
       
   881     </xsl:comment>
       
   882     <xsl:comment>
       
   883       <xsl:text>
       
   884 </xsl:text>
       
   885       <xsl:text>debug_unlink:
       
   886 </xsl:text>
       
   887       <xsl:call-template name="debug_unlink"/>
       
   888       <xsl:text>
       
   889 </xsl:text>
       
   890     </xsl:comment>
       
   891     <html xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/1999/xhtml">
       
   892       <head/>
       
   893       <body style="margin:0;overflow:hidden;">
       
   894         <xsl:copy-of select="$result_svg"/>
       
   895         <script>
       
   896           <xsl:call-template name="scripts"/>
       
   897         </script>
       
   898       </body>
       
   899     </html>
       
   900   </xsl:template>
       
   901   <xsl:template name="scripts">
       
   902     <xsl:text>
       
   903 </xsl:text>
       
   904     <xsl:text>id = idstr =&gt; document.getElementById(idstr);
       
   905 </xsl:text>
       
   906     <xsl:text>
       
   907 </xsl:text>
       
   908     <xsl:text>var hmi_hash = [</xsl:text>
       
   909     <xsl:value-of select="$hmitree/@hash"/>
       
   910     <xsl:text>]; 
       
   911 </xsl:text>
       
   912     <xsl:text>var hmi_widgets = {
       
   913 </xsl:text>
       
   914     <xsl:apply-templates mode="hmi_elements" select="$hmi_elements"/>
       
   915     <xsl:text>}
       
   916 </xsl:text>
       
   917     <xsl:text>
       
   918 </xsl:text>
       
   919     <xsl:text>var heartbeat_index = </xsl:text>
       
   920     <xsl:value-of select="$indexed_hmitree/*[@hmipath = '/HEARTBEAT']/@index"/>
       
   921     <xsl:text>;
       
   922 </xsl:text>
       
   923     <xsl:text>
       
   924 </xsl:text>
       
   925     <xsl:text>var hmitree_types = [
       
   926 </xsl:text>
       
   927     <xsl:for-each select="$indexed_hmitree/*">
       
   928       <xsl:text>    /* </xsl:text>
       
   929       <xsl:value-of select="@index"/>
       
   930       <xsl:text>  </xsl:text>
       
   931       <xsl:value-of select="@hmipath"/>
       
   932       <xsl:text> */ "</xsl:text>
       
   933       <xsl:value-of select="substring(local-name(), 5)"/>
       
   934       <xsl:text>"</xsl:text>
       
   935       <xsl:if test="position()!=last()">
       
   936         <xsl:text>,</xsl:text>
       
   937       </xsl:if>
       
   938       <xsl:text>
       
   939 </xsl:text>
       
   940     </xsl:for-each>
       
   941     <xsl:text>]
       
   942 </xsl:text>
       
   943     <xsl:text>
       
   944 </xsl:text>
       
   945     <xsl:text>var detachable_elements = {
       
   946 </xsl:text>
       
   947     <xsl:for-each select="$detachable_elements">
       
   948       <xsl:text>    "</xsl:text>
       
   949       <xsl:value-of select="@id"/>
       
   950       <xsl:text>":[id("</xsl:text>
       
   951       <xsl:value-of select="@id"/>
       
   952       <xsl:text>"), id("</xsl:text>
       
   953       <xsl:value-of select="../@id"/>
       
   954       <xsl:text>")]</xsl:text>
       
   955       <xsl:if test="position()!=last()">
       
   956         <xsl:text>,</xsl:text>
       
   957       </xsl:if>
       
   958       <xsl:text>
       
   959 </xsl:text>
       
   960     </xsl:for-each>
       
   961     <xsl:text>}
       
   962 </xsl:text>
       
   963     <xsl:text>
       
   964 </xsl:text>
       
   965     <xsl:text>var page_desc = {
       
   966 </xsl:text>
       
   967     <xsl:apply-templates mode="page_desc" select="$hmi_pages"/>
       
   968     <xsl:text>}
       
   969 </xsl:text>
       
   970     <xsl:text>
       
   971 </xsl:text>
       
   972     <xsl:text>var default_page = "</xsl:text>
       
   973     <xsl:value-of select="$default_page"/>
       
   974     <xsl:text>";
       
   975 </xsl:text>
       
   976     <xsl:text>var svg_root = id("</xsl:text>
       
   977     <xsl:value-of select="/svg:svg/@id"/>
       
   978     <xsl:text>");
       
   979 </xsl:text>
       
   980     <xsl:text>// svghmi.js
       
   981 </xsl:text>
       
   982     <xsl:text>
       
   983 </xsl:text>
       
   984     <xsl:text>var cache = hmitree_types.map(_ignored =&gt; undefined);
       
   985 </xsl:text>
       
   986     <xsl:text>var updates = {};
       
   987 </xsl:text>
       
   988     <xsl:text>
       
   989 </xsl:text>
       
   990     <xsl:text>function dispatch_value_to_widget(widget, index, value, oldval) {
       
   991 </xsl:text>
       
   992     <xsl:text>    try {
       
   993 </xsl:text>
       
   994     <xsl:text>        let idx = widget.offset ? index - widget.offset : index;
       
   995 </xsl:text>
       
   996     <xsl:text>        let idxidx = widget.indexes.indexOf(idx);
       
   997 </xsl:text>
       
   998     <xsl:text>        let d = widget.dispatch;
       
   999 </xsl:text>
       
  1000     <xsl:text>        console.log(index, idx, idxidx, value);
       
  1001 </xsl:text>
       
  1002     <xsl:text>        if(typeof(d) == "function" &amp;&amp; idxidx == 0){
       
  1003 </xsl:text>
       
  1004     <xsl:text>            d.call(widget, value, oldval);
       
  1005 </xsl:text>
       
  1006     <xsl:text>        }
       
  1007 </xsl:text>
       
  1008     <xsl:text>        else if(typeof(d) == "object" &amp;&amp; d.length &gt;= idxidx){
       
  1009 </xsl:text>
       
  1010     <xsl:text>            d[idxidx].call(widget, value, oldval);
       
  1011 </xsl:text>
       
  1012     <xsl:text>        }
       
  1013 </xsl:text>
       
  1014     <xsl:text>        /* else dispatch_0, ..., dispatch_n ? */
       
  1015 </xsl:text>
       
  1016     <xsl:text>        /*else {
       
  1017 </xsl:text>
       
  1018     <xsl:text>            throw new Error("Dunno how to dispatch to widget at index = " + index);
       
  1019 </xsl:text>
       
  1020     <xsl:text>        }*/
       
  1021 </xsl:text>
       
  1022     <xsl:text>    } catch(err) {
       
  1023 </xsl:text>
       
  1024     <xsl:text>        console.log(err);
       
  1025 </xsl:text>
       
  1026     <xsl:text>    }
       
  1027 </xsl:text>
       
  1028     <xsl:text>}
       
  1029 </xsl:text>
       
  1030     <xsl:text>
       
  1031 </xsl:text>
       
  1032     <xsl:text>function dispatch_value(index, value) {
       
  1033 </xsl:text>
       
  1034     <xsl:text>    let widgets = subscribers[index];
       
  1035 </xsl:text>
       
  1036     <xsl:text>
       
  1037 </xsl:text>
       
  1038     <xsl:text>    let oldval = cache[index];
       
  1039 </xsl:text>
       
  1040     <xsl:text>    cache[index] = value;
       
  1041 </xsl:text>
       
  1042     <xsl:text>
       
  1043 </xsl:text>
       
  1044     <xsl:text>    if(widgets.size &gt; 0) {
       
  1045 </xsl:text>
       
  1046     <xsl:text>        for(let widget of widgets){
       
  1047 </xsl:text>
       
  1048     <xsl:text>            dispatch_value_to_widget(widget, index, value, oldval);
       
  1049 </xsl:text>
       
  1050     <xsl:text>        }
       
  1051 </xsl:text>
       
  1052     <xsl:text>    }
       
  1053 </xsl:text>
       
  1054     <xsl:text>};
       
  1055 </xsl:text>
       
  1056     <xsl:text>
       
  1057 </xsl:text>
       
  1058     <xsl:text>function init_widgets() {
       
  1059 </xsl:text>
       
  1060     <xsl:text>    Object.keys(hmi_widgets).forEach(function(id) {
       
  1061 </xsl:text>
       
  1062     <xsl:text>        let widget = hmi_widgets[id];
       
  1063 </xsl:text>
       
  1064     <xsl:text>        let init = widget.init;
       
  1065 </xsl:text>
       
  1066     <xsl:text>        if(typeof(init) == "function"){
       
  1067 </xsl:text>
       
  1068     <xsl:text>            try {
       
  1069 </xsl:text>
       
  1070     <xsl:text>                init.call(widget);
       
  1071 </xsl:text>
       
  1072     <xsl:text>            } catch(err) {
       
  1073 </xsl:text>
       
  1074     <xsl:text>                console.log(err);
       
  1075 </xsl:text>
       
  1076     <xsl:text>            }
       
  1077 </xsl:text>
       
  1078     <xsl:text>        }
       
  1079 </xsl:text>
       
  1080     <xsl:text>    });
       
  1081 </xsl:text>
       
  1082     <xsl:text>};
       
  1083 </xsl:text>
       
  1084     <xsl:text>
       
  1085 </xsl:text>
       
  1086     <xsl:text>// Open WebSocket to relative "/ws" address
       
  1087 </xsl:text>
       
  1088     <xsl:text>var ws = new WebSocket(window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws'));
       
  1089 </xsl:text>
       
  1090     <xsl:text>ws.binaryType = 'arraybuffer';
       
  1091 </xsl:text>
       
  1092     <xsl:text>
       
  1093 </xsl:text>
       
  1094     <xsl:text>const dvgetters = {
       
  1095 </xsl:text>
       
  1096     <xsl:text>    INT: (dv,offset) =&gt; [dv.getInt16(offset, true), 2],
       
  1097 </xsl:text>
       
  1098     <xsl:text>    BOOL: (dv,offset) =&gt; [dv.getInt8(offset, true), 1],
       
  1099 </xsl:text>
       
  1100     <xsl:text>    STRING: (dv, offset) =&gt; {
       
  1101 </xsl:text>
       
  1102     <xsl:text>        size = dv.getInt8(offset);
       
  1103 </xsl:text>
       
  1104     <xsl:text>        return [
       
  1105 </xsl:text>
       
  1106     <xsl:text>            String.fromCharCode.apply(null, new Uint8Array(
       
  1107 </xsl:text>
       
  1108     <xsl:text>                dv.buffer, /* original buffer */
       
  1109 </xsl:text>
       
  1110     <xsl:text>                offset + 1, /* string starts after size*/
       
  1111 </xsl:text>
       
  1112     <xsl:text>                size /* size of string */
       
  1113 </xsl:text>
       
  1114     <xsl:text>            )), size + 1]; /* total increment */
       
  1115 </xsl:text>
       
  1116     <xsl:text>    }
       
  1117 </xsl:text>
       
  1118     <xsl:text>};
       
  1119 </xsl:text>
       
  1120     <xsl:text>
       
  1121 </xsl:text>
       
  1122     <xsl:text>// Apply updates recieved through ws.onmessage to subscribed widgets
       
  1123 </xsl:text>
       
  1124     <xsl:text>function apply_updates() {
       
  1125 </xsl:text>
       
  1126     <xsl:text>    for(let index in updates){
       
  1127 </xsl:text>
       
  1128     <xsl:text>        // serving as a key, index becomes a string
       
  1129 </xsl:text>
       
  1130     <xsl:text>        // -&gt; pass Number(index) instead
       
  1131 </xsl:text>
       
  1132     <xsl:text>        dispatch_value(Number(index), updates[index]);
       
  1133 </xsl:text>
       
  1134     <xsl:text>        delete updates[index];
       
  1135 </xsl:text>
       
  1136     <xsl:text>    }
       
  1137 </xsl:text>
       
  1138     <xsl:text>}
       
  1139 </xsl:text>
       
  1140     <xsl:text>
       
  1141 </xsl:text>
       
  1142     <xsl:text>// Called on requestAnimationFrame, modifies DOM
       
  1143 </xsl:text>
       
  1144     <xsl:text>var requestAnimationFrameID = null;
       
  1145 </xsl:text>
       
  1146     <xsl:text>function animate() {
       
  1147 </xsl:text>
       
  1148     <xsl:text>    // Do the page swith if any one pending
       
  1149 </xsl:text>
       
  1150     <xsl:text>    if(current_subscribed_page != current_visible_page){
       
  1151 </xsl:text>
       
  1152     <xsl:text>        switch_visible_page(current_subscribed_page);
       
  1153 </xsl:text>
       
  1154     <xsl:text>    }
       
  1155 </xsl:text>
       
  1156     <xsl:text>    apply_updates();
       
  1157 </xsl:text>
       
  1158     <xsl:text>    requestAnimationFrameID = null;
       
  1159 </xsl:text>
       
  1160     <xsl:text>}
       
  1161 </xsl:text>
       
  1162     <xsl:text>
       
  1163 </xsl:text>
       
  1164     <xsl:text>function requestHMIAnimation() {
       
  1165 </xsl:text>
       
  1166     <xsl:text>    if(requestAnimationFrameID == null){
       
  1167 </xsl:text>
       
  1168     <xsl:text>        requestAnimationFrameID = window.requestAnimationFrame(animate);
       
  1169 </xsl:text>
       
  1170     <xsl:text>    }
       
  1171 </xsl:text>
       
  1172     <xsl:text>}
       
  1173 </xsl:text>
       
  1174     <xsl:text>
       
  1175 </xsl:text>
       
  1176     <xsl:text>// Message reception handler
       
  1177 </xsl:text>
       
  1178     <xsl:text>// Hash is verified and HMI values updates resulting from binary parsing
       
  1179 </xsl:text>
       
  1180     <xsl:text>// are stored until browser can compute next frame, DOM is left untouched
       
  1181 </xsl:text>
       
  1182     <xsl:text>ws.onmessage = function (evt) {
       
  1183 </xsl:text>
       
  1184     <xsl:text>
       
  1185 </xsl:text>
       
  1186     <xsl:text>    let data = evt.data;
       
  1187 </xsl:text>
       
  1188     <xsl:text>    let dv = new DataView(data);
       
  1189 </xsl:text>
       
  1190     <xsl:text>    let i = 0;
       
  1191 </xsl:text>
       
  1192     <xsl:text>    try {
       
  1193 </xsl:text>
       
  1194     <xsl:text>        for(let hash_int of hmi_hash) {
       
  1195 </xsl:text>
       
  1196     <xsl:text>            if(hash_int != dv.getUint8(i)){
       
  1197 </xsl:text>
       
  1198     <xsl:text>                throw new Error("Hash doesn't match");
       
  1199 </xsl:text>
       
  1200     <xsl:text>            };
       
  1201 </xsl:text>
       
  1202     <xsl:text>            i++;
       
  1203 </xsl:text>
       
  1204     <xsl:text>        };
       
  1205 </xsl:text>
       
  1206     <xsl:text>
       
  1207 </xsl:text>
       
  1208     <xsl:text>        while(i &lt; data.byteLength){
       
  1209 </xsl:text>
       
  1210     <xsl:text>            let index = dv.getUint32(i, true);
       
  1211 </xsl:text>
       
  1212     <xsl:text>            i += 4;
       
  1213 </xsl:text>
       
  1214     <xsl:text>            let iectype = hmitree_types[index];
       
  1215 </xsl:text>
       
  1216     <xsl:text>            if(iectype != undefined){
       
  1217 </xsl:text>
       
  1218     <xsl:text>                let dvgetter = dvgetters[iectype];
       
  1219 </xsl:text>
       
  1220     <xsl:text>                let [value, bytesize] = dvgetter(dv,i);
       
  1221 </xsl:text>
       
  1222     <xsl:text>                updates[index] = value;
       
  1223 </xsl:text>
       
  1224     <xsl:text>                i += bytesize;
       
  1225 </xsl:text>
       
  1226     <xsl:text>            } else {
       
  1227 </xsl:text>
       
  1228     <xsl:text>                throw new Error("Unknown index "+index);
       
  1229 </xsl:text>
       
  1230     <xsl:text>            }
       
  1231 </xsl:text>
       
  1232     <xsl:text>        };
       
  1233 </xsl:text>
       
  1234     <xsl:text>        // register for rendering on next frame, since there are updates
       
  1235 </xsl:text>
       
  1236     <xsl:text>        requestHMIAnimation();
       
  1237 </xsl:text>
       
  1238     <xsl:text>    } catch(err) {
       
  1239 </xsl:text>
       
  1240     <xsl:text>        // 1003 is for "Unsupported Data"
       
  1241 </xsl:text>
       
  1242     <xsl:text>        // ws.close(1003, err.message);
       
  1243 </xsl:text>
       
  1244     <xsl:text>
       
  1245 </xsl:text>
       
  1246     <xsl:text>        // TODO : remove debug alert ?
       
  1247 </xsl:text>
       
  1248     <xsl:text>        alert("Error : "+err.message+"\nHMI will be reloaded.");
       
  1249 </xsl:text>
       
  1250     <xsl:text>
       
  1251 </xsl:text>
       
  1252     <xsl:text>        // force reload ignoring cache
       
  1253 </xsl:text>
       
  1254     <xsl:text>        location.reload(true);
       
  1255 </xsl:text>
       
  1256     <xsl:text>    }
       
  1257 </xsl:text>
       
  1258     <xsl:text>};
       
  1259 </xsl:text>
       
  1260     <xsl:text>
       
  1261 </xsl:text>
       
  1262     <xsl:text>
       
  1263 </xsl:text>
       
  1264     <xsl:text>function send_blob(data) {
       
  1265 </xsl:text>
       
  1266     <xsl:text>    if(data.length &gt; 0) {
       
  1267 </xsl:text>
       
  1268     <xsl:text>        ws.send(new Blob([new Uint8Array(hmi_hash)].concat(data)));
       
  1269 </xsl:text>
       
  1270     <xsl:text>    };
       
  1271 </xsl:text>
       
  1272     <xsl:text>};
       
  1273 </xsl:text>
       
  1274     <xsl:text>
       
  1275 </xsl:text>
       
  1276     <xsl:text>const typedarray_types = {
       
  1277 </xsl:text>
       
  1278     <xsl:text>    INT: (number) =&gt; new Int16Array([number]),
       
  1279 </xsl:text>
       
  1280     <xsl:text>    BOOL: (truth) =&gt; new Int16Array([truth]),
       
  1281 </xsl:text>
       
  1282     <xsl:text>    STRING: (str) =&gt; {
       
  1283 </xsl:text>
       
  1284     <xsl:text>        // beremiz default string max size is 128
       
  1285 </xsl:text>
       
  1286     <xsl:text>        str = str.slice(0,128);
       
  1287 </xsl:text>
       
  1288     <xsl:text>        binary = new Uint8Array(str.length + 1);
       
  1289 </xsl:text>
       
  1290     <xsl:text>        binary[0] = str.length;
       
  1291 </xsl:text>
       
  1292     <xsl:text>        for(var i = 0; i &lt; str.length; i++){
       
  1293 </xsl:text>
       
  1294     <xsl:text>            binary[i+1] = str.charCodeAt(i);
       
  1295 </xsl:text>
       
  1296     <xsl:text>        }
       
  1297 </xsl:text>
       
  1298     <xsl:text>        return binary;
       
  1299 </xsl:text>
       
  1300     <xsl:text>    }
       
  1301 </xsl:text>
       
  1302     <xsl:text>    /* TODO */
       
  1303 </xsl:text>
       
  1304     <xsl:text>};
       
  1305 </xsl:text>
       
  1306     <xsl:text>
       
  1307 </xsl:text>
       
  1308     <xsl:text>function send_reset() {
       
  1309 </xsl:text>
       
  1310     <xsl:text>    send_blob(new Uint8Array([1])); /* reset = 1 */
       
  1311 </xsl:text>
       
  1312     <xsl:text>};
       
  1313 </xsl:text>
       
  1314     <xsl:text>
       
  1315 </xsl:text>
       
  1316     <xsl:text>// subscription state, as it should be in hmi server
       
  1317 </xsl:text>
       
  1318     <xsl:text>// hmitree indexed array of integers
       
  1319 </xsl:text>
       
  1320     <xsl:text>var subscriptions =  hmitree_types.map(_ignored =&gt; 0);
       
  1321 </xsl:text>
       
  1322     <xsl:text>
       
  1323 </xsl:text>
       
  1324     <xsl:text>// subscription state as needed by widget now
       
  1325 </xsl:text>
       
  1326     <xsl:text>// hmitree indexed array of Sets of widgets objects
       
  1327 </xsl:text>
       
  1328     <xsl:text>var subscribers = hmitree_types.map(_ignored =&gt; new Set());
       
  1329 </xsl:text>
       
  1330     <xsl:text>
       
  1331 </xsl:text>
       
  1332     <xsl:text>// artificially subscribe the watchdog widget to "/heartbeat" hmi variable
       
  1333 </xsl:text>
       
  1334     <xsl:text>// Since dispatch directly calls change_hmi_value,
       
  1335 </xsl:text>
       
  1336     <xsl:text>// PLC will periodically send variable at given frequency
       
  1337 </xsl:text>
       
  1338     <xsl:text>subscribers[heartbeat_index].add({
       
  1339 </xsl:text>
       
  1340     <xsl:text>    /* type: "Watchdog", */
       
  1341 </xsl:text>
       
  1342     <xsl:text>    frequency: 1,
       
  1343 </xsl:text>
       
  1344     <xsl:text>    indexes: [heartbeat_index],
       
  1345 </xsl:text>
       
  1346     <xsl:text>    dispatch: function(value) {
       
  1347 </xsl:text>
       
  1348     <xsl:text>        // console.log("Heartbeat" + value);
       
  1349 </xsl:text>
       
  1350     <xsl:text>        change_hmi_value(heartbeat_index, "+1");
       
  1351 </xsl:text>
       
  1352     <xsl:text>    }
       
  1353 </xsl:text>
       
  1354     <xsl:text>});
       
  1355 </xsl:text>
       
  1356     <xsl:text>
       
  1357 </xsl:text>
       
  1358     <xsl:text>function update_subscriptions() {
       
  1359 </xsl:text>
       
  1360     <xsl:text>    let delta = [];
       
  1361 </xsl:text>
       
  1362     <xsl:text>    for(let index = 0; index &lt; subscribers.length; index++){
       
  1363 </xsl:text>
       
  1364     <xsl:text>        let widgets = subscribers[index];
       
  1365 </xsl:text>
       
  1366     <xsl:text>
       
  1367 </xsl:text>
       
  1368     <xsl:text>        // periods are in ms
       
  1369 </xsl:text>
       
  1370     <xsl:text>        let previous_period = subscriptions[index];
       
  1371 </xsl:text>
       
  1372     <xsl:text>
       
  1373 </xsl:text>
       
  1374     <xsl:text>        // subscribing with a zero period is unsubscribing
       
  1375 </xsl:text>
       
  1376     <xsl:text>        let new_period = 0;
       
  1377 </xsl:text>
       
  1378     <xsl:text>        if(widgets.size &gt; 0) {
       
  1379 </xsl:text>
       
  1380     <xsl:text>            let maxfreq = 0;
       
  1381 </xsl:text>
       
  1382     <xsl:text>            for(let widget of widgets)
       
  1383 </xsl:text>
       
  1384     <xsl:text>                if(maxfreq &lt; widget.frequency)
       
  1385 </xsl:text>
       
  1386     <xsl:text>                    maxfreq = widget.frequency;
       
  1387 </xsl:text>
       
  1388     <xsl:text>
       
  1389 </xsl:text>
       
  1390     <xsl:text>            if(maxfreq != 0)
       
  1391 </xsl:text>
       
  1392     <xsl:text>                new_period = 1000/maxfreq;
       
  1393 </xsl:text>
       
  1394     <xsl:text>        }
       
  1395 </xsl:text>
       
  1396     <xsl:text>
       
  1397 </xsl:text>
       
  1398     <xsl:text>        if(previous_period != new_period) {
       
  1399 </xsl:text>
       
  1400     <xsl:text>            subscriptions[index] = new_period;
       
  1401 </xsl:text>
       
  1402     <xsl:text>            delta.push(
       
  1403 </xsl:text>
       
  1404     <xsl:text>                new Uint8Array([2]), /* subscribe = 2 */
       
  1405 </xsl:text>
       
  1406     <xsl:text>                new Uint32Array([index]),
       
  1407 </xsl:text>
       
  1408     <xsl:text>                new Uint16Array([new_period]));
       
  1409 </xsl:text>
       
  1410     <xsl:text>        }
       
  1411 </xsl:text>
       
  1412     <xsl:text>    }
       
  1413 </xsl:text>
       
  1414     <xsl:text>    send_blob(delta);
       
  1415 </xsl:text>
       
  1416     <xsl:text>};
       
  1417 </xsl:text>
       
  1418     <xsl:text>
       
  1419 </xsl:text>
       
  1420     <xsl:text>function send_hmi_value(index, value) {
       
  1421 </xsl:text>
       
  1422     <xsl:text>    let iectype = hmitree_types[index];
       
  1423 </xsl:text>
       
  1424     <xsl:text>    let tobinary = typedarray_types[iectype];
       
  1425 </xsl:text>
       
  1426     <xsl:text>    send_blob([
       
  1427 </xsl:text>
       
  1428     <xsl:text>        new Uint8Array([0]),  /* setval = 0 */
       
  1429 </xsl:text>
       
  1430     <xsl:text>        new Uint32Array([index]),
       
  1431 </xsl:text>
       
  1432     <xsl:text>        tobinary(value)]);
       
  1433 </xsl:text>
       
  1434     <xsl:text>
       
  1435 </xsl:text>
       
  1436     <xsl:text>    cache[index] = value;
       
  1437 </xsl:text>
       
  1438     <xsl:text>};
       
  1439 </xsl:text>
       
  1440     <xsl:text>
       
  1441 </xsl:text>
       
  1442     <xsl:text>function change_hmi_value(index, opstr) {
       
  1443 </xsl:text>
       
  1444     <xsl:text>    let op = opstr[0];
       
  1445 </xsl:text>
       
  1446     <xsl:text>    let given_val = opstr.slice(1);
       
  1447 </xsl:text>
       
  1448     <xsl:text>    let old_val = cache[index]
       
  1449 </xsl:text>
       
  1450     <xsl:text>    let new_val;
       
  1451 </xsl:text>
       
  1452     <xsl:text>    switch(op){
       
  1453 </xsl:text>
       
  1454     <xsl:text>      case "=":
       
  1455 </xsl:text>
       
  1456     <xsl:text>        eval("new_val"+opstr);
       
  1457 </xsl:text>
       
  1458     <xsl:text>        break;
       
  1459 </xsl:text>
       
  1460     <xsl:text>      case "+":
       
  1461 </xsl:text>
       
  1462     <xsl:text>      case "-":
       
  1463 </xsl:text>
       
  1464     <xsl:text>      case "*":
       
  1465 </xsl:text>
       
  1466     <xsl:text>      case "/":
       
  1467 </xsl:text>
       
  1468     <xsl:text>        if(old_val != undefined)
       
  1469 </xsl:text>
       
  1470     <xsl:text>            new_val = eval("old_val"+opstr);
       
  1471 </xsl:text>
       
  1472     <xsl:text>        break;
       
  1473 </xsl:text>
       
  1474     <xsl:text>    }
       
  1475 </xsl:text>
       
  1476     <xsl:text>    if(new_val != undefined &amp;&amp; old_val != new_val)
       
  1477 </xsl:text>
       
  1478     <xsl:text>        send_hmi_value(index, new_val);
       
  1479 </xsl:text>
       
  1480     <xsl:text>    return new_val;
       
  1481 </xsl:text>
       
  1482     <xsl:text>}
       
  1483 </xsl:text>
       
  1484     <xsl:text>
       
  1485 </xsl:text>
       
  1486     <xsl:text>var current_visible_page;
       
  1487 </xsl:text>
       
  1488     <xsl:text>var current_subscribed_page;
       
  1489 </xsl:text>
       
  1490     <xsl:text>
       
  1491 </xsl:text>
       
  1492     <xsl:text>function prepare_svg() {
       
  1493 </xsl:text>
       
  1494     <xsl:text>    for(let eltid in detachable_elements){
       
  1495 </xsl:text>
       
  1496     <xsl:text>        let [element,parent] = detachable_elements[eltid];
       
  1497 </xsl:text>
       
  1498     <xsl:text>        parent.removeChild(element);
       
  1499 </xsl:text>
       
  1500     <xsl:text>    }
       
  1501 </xsl:text>
       
  1502     <xsl:text>};
       
  1503 </xsl:text>
       
  1504     <xsl:text>
       
  1505 </xsl:text>
       
  1506     <xsl:text>function switch_page(page_name, page_index) {
       
  1507 </xsl:text>
       
  1508     <xsl:text>    if(current_subscribed_page != current_visible_page){
       
  1509 </xsl:text>
       
  1510     <xsl:text>        /* page switch already going */
       
  1511 </xsl:text>
       
  1512     <xsl:text>        /* TODO LOG ERROR */
       
  1513 </xsl:text>
       
  1514     <xsl:text>        return;
       
  1515 </xsl:text>
       
  1516     <xsl:text>    } else if(page_name == current_visible_page){
       
  1517 </xsl:text>
       
  1518     <xsl:text>        /* already in that page */
       
  1519 </xsl:text>
       
  1520     <xsl:text>        /* TODO LOG ERROR */
       
  1521 </xsl:text>
       
  1522     <xsl:text>        return;
       
  1523 </xsl:text>
       
  1524     <xsl:text>    }
       
  1525 </xsl:text>
       
  1526     <xsl:text>    switch_subscribed_page(page_name, page_index);
       
  1527 </xsl:text>
       
  1528     <xsl:text>};
       
  1529 </xsl:text>
       
  1530     <xsl:text>
       
  1531 </xsl:text>
       
  1532     <xsl:text>function* chain(a,b){
       
  1533 </xsl:text>
       
  1534     <xsl:text>    yield* a;
       
  1535 </xsl:text>
       
  1536     <xsl:text>    yield* b;
       
  1537 </xsl:text>
       
  1538     <xsl:text>};
       
  1539 </xsl:text>
       
  1540     <xsl:text>
       
  1541 </xsl:text>
       
  1542     <xsl:text>function switch_subscribed_page(page_name, page_index) {
       
  1543 </xsl:text>
       
  1544     <xsl:text>    let old_desc = page_desc[current_subscribed_page];
       
  1545 </xsl:text>
       
  1546     <xsl:text>    let new_desc = page_desc[page_name];
       
  1547 </xsl:text>
       
  1548     <xsl:text>
       
  1549 </xsl:text>
       
  1550     <xsl:text>    if(new_desc == undefined){
       
  1551 </xsl:text>
       
  1552     <xsl:text>        /* TODO LOG ERROR */
       
  1553 </xsl:text>
       
  1554     <xsl:text>        return;
       
  1555 </xsl:text>
       
  1556     <xsl:text>    }
       
  1557 </xsl:text>
       
  1558     <xsl:text>
       
  1559 </xsl:text>
       
  1560     <xsl:text>    if(page_index == undefined){
       
  1561 </xsl:text>
       
  1562     <xsl:text>        page_index = new_desc.page_index;
       
  1563 </xsl:text>
       
  1564     <xsl:text>    }
       
  1565 </xsl:text>
       
  1566     <xsl:text>
       
  1567 </xsl:text>
       
  1568     <xsl:text>    if(old_desc){
       
  1569 </xsl:text>
       
  1570     <xsl:text>        for(let widget of old_desc.absolute_widgets){
       
  1571 </xsl:text>
       
  1572     <xsl:text>            /* remove subsribers */
       
  1573 </xsl:text>
       
  1574     <xsl:text>            for(let index of widget.indexes){
       
  1575 </xsl:text>
       
  1576     <xsl:text>                subscribers[index].delete(widget);
       
  1577 </xsl:text>
       
  1578     <xsl:text>            }
       
  1579 </xsl:text>
       
  1580     <xsl:text>        }
       
  1581 </xsl:text>
       
  1582     <xsl:text>        for(let widget of old_desc.relative_widgets){
       
  1583 </xsl:text>
       
  1584     <xsl:text>            /* remove subsribers */
       
  1585 </xsl:text>
       
  1586     <xsl:text>            for(let index of widget.indexes){
       
  1587 </xsl:text>
       
  1588     <xsl:text>                let idx = widget.offset ? index + widget.offset : index;
       
  1589 </xsl:text>
       
  1590     <xsl:text>                subscribers[idx].delete(widget);
       
  1591 </xsl:text>
       
  1592     <xsl:text>            }
       
  1593 </xsl:text>
       
  1594     <xsl:text>            /* lose the offset */
       
  1595 </xsl:text>
       
  1596     <xsl:text>            delete widget.offset;
       
  1597 </xsl:text>
       
  1598     <xsl:text>        }
       
  1599 </xsl:text>
       
  1600     <xsl:text>    }
       
  1601 </xsl:text>
       
  1602     <xsl:text>    for(let widget of new_desc.absolute_widgets){
       
  1603 </xsl:text>
       
  1604     <xsl:text>        /* add widget's subsribers */
       
  1605 </xsl:text>
       
  1606     <xsl:text>        for(let index of widget.indexes){
       
  1607 </xsl:text>
       
  1608     <xsl:text>            subscribers[index].add(widget);
       
  1609 </xsl:text>
       
  1610     <xsl:text>        }
       
  1611 </xsl:text>
       
  1612     <xsl:text>    }
       
  1613 </xsl:text>
       
  1614     <xsl:text>    var new_offset = page_index == undefined ? 0 : page_index - new_desc.page_index;
       
  1615 </xsl:text>
       
  1616     <xsl:text>    for(let widget of new_desc.relative_widgets){
       
  1617 </xsl:text>
       
  1618     <xsl:text>        /* set the offset because relative */
       
  1619 </xsl:text>
       
  1620     <xsl:text>        widget.offset = new_offset;
       
  1621 </xsl:text>
       
  1622     <xsl:text>        /* add widget's subsribers */
       
  1623 </xsl:text>
       
  1624     <xsl:text>        for(let index of widget.indexes){
       
  1625 </xsl:text>
       
  1626     <xsl:text>            subscribers[index + new_offset].add(widget);
       
  1627 </xsl:text>
       
  1628     <xsl:text>        }
       
  1629 </xsl:text>
       
  1630     <xsl:text>    }
       
  1631 </xsl:text>
       
  1632     <xsl:text>
       
  1633 </xsl:text>
       
  1634     <xsl:text>    update_subscriptions();
       
  1635 </xsl:text>
       
  1636     <xsl:text>
       
  1637 </xsl:text>
       
  1638     <xsl:text>    current_subscribed_page = page_name;
       
  1639 </xsl:text>
       
  1640     <xsl:text>
       
  1641 </xsl:text>
       
  1642     <xsl:text>    requestHMIAnimation();
       
  1643 </xsl:text>
       
  1644     <xsl:text>}
       
  1645 </xsl:text>
       
  1646     <xsl:text>
       
  1647 </xsl:text>
       
  1648     <xsl:text>function switch_visible_page(page_name) {
       
  1649 </xsl:text>
       
  1650     <xsl:text>
       
  1651 </xsl:text>
       
  1652     <xsl:text>    let old_desc = page_desc[current_visible_page];
       
  1653 </xsl:text>
       
  1654     <xsl:text>    let new_desc = page_desc[page_name];
       
  1655 </xsl:text>
       
  1656     <xsl:text>
       
  1657 </xsl:text>
       
  1658     <xsl:text>    if(old_desc){
       
  1659 </xsl:text>
       
  1660     <xsl:text>        for(let eltid in old_desc.required_detachables){
       
  1661 </xsl:text>
       
  1662     <xsl:text>            if(!(eltid in new_desc.required_detachables)){
       
  1663 </xsl:text>
       
  1664     <xsl:text>                let [element, parent] = old_desc.required_detachables[eltid];
       
  1665 </xsl:text>
       
  1666     <xsl:text>                parent.removeChild(element);
       
  1667 </xsl:text>
       
  1668     <xsl:text>            }
       
  1669 </xsl:text>
       
  1670     <xsl:text>        }
       
  1671 </xsl:text>
       
  1672     <xsl:text>        for(let eltid in new_desc.required_detachables){
       
  1673 </xsl:text>
       
  1674     <xsl:text>            if(!(eltid in old_desc.required_detachables)){
       
  1675 </xsl:text>
       
  1676     <xsl:text>                let [element, parent] = new_desc.required_detachables[eltid];
       
  1677 </xsl:text>
       
  1678     <xsl:text>                parent.appendChild(element);
       
  1679 </xsl:text>
       
  1680     <xsl:text>            }
       
  1681 </xsl:text>
       
  1682     <xsl:text>        }
       
  1683 </xsl:text>
       
  1684     <xsl:text>    }else{
       
  1685 </xsl:text>
       
  1686     <xsl:text>        for(let eltid in new_desc.required_detachables){
       
  1687 </xsl:text>
       
  1688     <xsl:text>            let [element, parent] = new_desc.required_detachables[eltid];
       
  1689 </xsl:text>
       
  1690     <xsl:text>            parent.appendChild(element);
       
  1691 </xsl:text>
       
  1692     <xsl:text>        }
       
  1693 </xsl:text>
       
  1694     <xsl:text>    }
       
  1695 </xsl:text>
       
  1696     <xsl:text>
       
  1697 </xsl:text>
       
  1698     <xsl:text>    for(let widget of chain(new_desc.absolute_widgets,new_desc.relative_widgets)){
       
  1699 </xsl:text>
       
  1700     <xsl:text>        for(let index of widget.indexes){
       
  1701 </xsl:text>
       
  1702     <xsl:text>            /* dispatch current cache in newly opened page widgets */
       
  1703 </xsl:text>
       
  1704     <xsl:text>            let cached_val = cache[index];
       
  1705 </xsl:text>
       
  1706     <xsl:text>            if(cached_val != undefined)
       
  1707 </xsl:text>
       
  1708     <xsl:text>                dispatch_value_to_widget(widget, index, cached_val, cached_val);
       
  1709 </xsl:text>
       
  1710     <xsl:text>        }
       
  1711 </xsl:text>
       
  1712     <xsl:text>    }
       
  1713 </xsl:text>
       
  1714     <xsl:text>
       
  1715 </xsl:text>
       
  1716     <xsl:text>    svg_root.setAttribute('viewBox',new_desc.bbox.join(" "));
       
  1717 </xsl:text>
       
  1718     <xsl:text>    current_visible_page = page_name;
       
  1719 </xsl:text>
       
  1720     <xsl:text>};
       
  1721 </xsl:text>
       
  1722     <xsl:text>
       
  1723 </xsl:text>
       
  1724     <xsl:text>
       
  1725 </xsl:text>
       
  1726     <xsl:text>// Once connection established
       
  1727 </xsl:text>
       
  1728     <xsl:text>ws.onopen = function (evt) {
       
  1729 </xsl:text>
       
  1730     <xsl:text>    init_widgets();
       
  1731 </xsl:text>
       
  1732     <xsl:text>    send_reset();
       
  1733 </xsl:text>
       
  1734     <xsl:text>    // show main page
       
  1735 </xsl:text>
       
  1736     <xsl:text>    prepare_svg();
       
  1737 </xsl:text>
       
  1738     <xsl:text>    switch_page(default_page);
       
  1739 </xsl:text>
       
  1740     <xsl:text>};
       
  1741 </xsl:text>
       
  1742     <xsl:text>
       
  1743 </xsl:text>
       
  1744     <xsl:text>ws.onclose = function (evt) {
       
  1745 </xsl:text>
       
  1746     <xsl:text>    // TODO : add visible notification while waiting for reload
       
  1747 </xsl:text>
       
  1748     <xsl:text>    console.log("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+" Reload in 10s.");
       
  1749 </xsl:text>
       
  1750     <xsl:text>    // TODO : re-enable auto reload when not in debug
       
  1751 </xsl:text>
       
  1752     <xsl:text>    //window.setTimeout(() =&gt; location.reload(true), 10000);
       
  1753 </xsl:text>
       
  1754     <xsl:text>    alert("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+".");
       
  1755 </xsl:text>
       
  1756     <xsl:text>
       
  1757 </xsl:text>
       
  1758     <xsl:text>};
  1768 </xsl:text>
  1759 </xsl:text>
  1769   </xsl:template>
  1760   </xsl:template>
  1770 </xsl:stylesheet>
  1761 </xsl:stylesheet>