svghmi/svghmi.js
branchwxPython4
changeset 3657 e0d6f5f0dcc2
parent 3653 d5ff60e906b0
child 3661 62860665fa94
equal deleted inserted replaced
3652:b5c6bb72bfc9 3657:e0d6f5f0dcc2
    20     });
    20     });
    21 };
    21 };
    22 
    22 
    23 // Open WebSocket to relative "/ws" address
    23 // Open WebSocket to relative "/ws" address
    24 var has_watchdog = window.location.hash == "#watchdog";
    24 var has_watchdog = window.location.hash == "#watchdog";
    25 
       
    26 var ws_url = 
       
    27     window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws')
       
    28     + '?mode=' + (has_watchdog ? "watchdog" : "multiclient");
       
    29 
       
    30 var ws = new WebSocket(ws_url);
       
    31 ws.binaryType = 'arraybuffer';
       
    32 
    25 
    33 const dvgetters = {
    26 const dvgetters = {
    34     INT: (dv,offset) => [dv.getInt16(offset, true), 2],
    27     INT: (dv,offset) => [dv.getInt16(offset, true), 2],
    35     BOOL: (dv,offset) => [dv.getInt8(offset, true), 1],
    28     BOOL: (dv,offset) => [dv.getInt8(offset, true), 1],
    36     NODE: (dv,offset) => [dv.getInt8(offset, true), 1],
    29     NODE: (dv,offset) => [dv.getInt8(offset, true), 1],
    96 }
    89 }
    97 
    90 
    98 // Message reception handler
    91 // Message reception handler
    99 // Hash is verified and HMI values updates resulting from binary parsing
    92 // Hash is verified and HMI values updates resulting from binary parsing
   100 // are stored until browser can compute next frame, DOM is left untouched
    93 // are stored until browser can compute next frame, DOM is left untouched
   101 ws.onmessage = function (evt) {
    94 function ws_onmessage(evt) {
   102 
    95 
   103     let data = evt.data;
    96     let data = evt.data;
   104     let dv = new DataView(data);
    97     let dv = new DataView(data);
   105     let i = 0;
    98     let i = 0;
   106     try {
    99     try {
   138     }
   131     }
   139 };
   132 };
   140 
   133 
   141 hmi_hash_u8 = new Uint8Array(hmi_hash);
   134 hmi_hash_u8 = new Uint8Array(hmi_hash);
   142 
   135 
       
   136 var ws = null;
       
   137 
   143 function send_blob(data) {
   138 function send_blob(data) {
   144     if(data.length > 0) {
   139     if(ws && data.length > 0) {
   145         ws.send(new Blob([hmi_hash_u8].concat(data)));
   140         ws.send(new Blob([hmi_hash_u8].concat(data)));
   146     };
   141     };
   147 };
   142 };
   148 
   143 
   149 const typedarray_types = {
   144 const typedarray_types = {
   150     INT: (number) => new Int16Array([number]),
   145     INT: (number) => new Int16Array([number]),
   151     BOOL: (truth) => new Int16Array([truth]),
   146     BOOL: (truth) => new Int8Array([truth]),
   152     NODE: (truth) => new Int16Array([truth]),
   147     NODE: (truth) => new Int8Array([truth]),
   153     REAL: (number) => new Float32Array([number]),
   148     REAL: (number) => new Float32Array([number]),
   154     STRING: (str) => {
   149     STRING: (str) => {
   155         // beremiz default string max size is 128
   150         // beremiz default string max size is 128
   156         str = str.slice(0,128);
   151         str = str.slice(0,128);
   157         binary = new Uint8Array(str.length + 1);
   152         binary = new Uint8Array(str.length + 1);
   195     if(entry == undefined){
   190     if(entry == undefined){
   196         subscriptions[index] = [new Set(), period];
   191         subscriptions[index] = [new Set(), period];
   197     } else {
   192     } else {
   198         entry[1] = period;
   193         entry[1] = period;
   199     }
   194     }
       
   195 }
       
   196 
       
   197 function reset_subscription_periods() {
       
   198     for(let index in subscriptions)
       
   199         subscriptions[index][1] = 0;
   200 }
   200 }
   201 
   201 
   202 if(has_watchdog){
   202 if(has_watchdog){
   203     // artificially subscribe the watchdog widget to "/heartbeat" hmi variable
   203     // artificially subscribe the watchdog widget to "/heartbeat" hmi variable
   204     // Since dispatch directly calls change_hmi_value,
   204     // Since dispatch directly calls change_hmi_value,
   296 
   296 
   297 setup_lang();
   297 setup_lang();
   298 
   298 
   299 function update_subscriptions() {
   299 function update_subscriptions() {
   300     let delta = [];
   300     let delta = [];
       
   301     if(!ws)
       
   302         // dont' change subscriptions if not connected
       
   303         return;
       
   304 
   301     for(let index in subscriptions){
   305     for(let index in subscriptions){
   302         let widgets = subscribers(index);
   306         let widgets = subscribers(index);
   303 
   307 
   304         // periods are in ms
   308         // periods are in ms
   305         let previous_period = get_subscription_period(index);
   309         let previous_period = get_subscription_period(index);
   416   } else {
   420   } else {
   417     document.exitFullscreen();
   421     document.exitFullscreen();
   418   }
   422   }
   419 }
   423 }
   420 
   424 
   421 function prepare_svg() {
   425 // prevents context menu from appearing on right click and long touch
   422     // prevents context menu from appearing on right click and long touch
   426 document.body.addEventListener('contextmenu', e => {
   423     document.body.addEventListener('contextmenu', e => {
   427     toggleFullscreen();
   424         toggleFullscreen();
   428     e.preventDefault();
   425         e.preventDefault();
   429 });
   426     });
   430 
       
   431 var screensaver_timer = null;
       
   432 function reset_screensaver_timer() {
       
   433     if(screensaver_timer){
       
   434         window.clearTimeout(screensaver_timer);
       
   435     }
       
   436     screensaver_timer = window.setTimeout(() => {
       
   437         switch_page("ScreenSaver");
       
   438         screensaver_timer = null;
       
   439     }, screensaver_delay*1000);
       
   440 }
       
   441 if(screensaver_delay)
       
   442     document.body.addEventListener('pointerdown', reset_screensaver_timer);
       
   443 
       
   444 function detach_detachables() {
   427 
   445 
   428     for(let eltid in detachable_elements){
   446     for(let eltid in detachable_elements){
   429         let [element,parent] = detachable_elements[eltid];
   447         let [element,parent] = detachable_elements[eltid];
   430         parent.removeChild(element);
   448         parent.removeChild(element);
   431     }
   449     }
   490     apply_hmi_value(page_node_local_index, page_node);
   508     apply_hmi_value(page_node_local_index, page_node);
   491 
   509 
   492     jumps_need_update = true;
   510     jumps_need_update = true;
   493 
   511 
   494     requestHMIAnimation();
   512     requestHMIAnimation();
   495     jump_history.push([page_name, page_index]);
   513     let [last_page_name, last_page_index] = jump_history[jump_history.length-1];
   496     if(jump_history.length > 42)
   514     if(last_page_name != page_name || last_page_index != page_index){
   497         jump_history.shift();
   515         jump_history.push([page_name, page_index]);
       
   516         if(jump_history.length > 42)
       
   517             jump_history.shift();
       
   518     }
   498 
   519 
   499     apply_hmi_value(current_page_var_index, page_index == undefined
   520     apply_hmi_value(current_page_var_index, page_index == undefined
   500         ? page_name
   521         ? page_name
   501         : page_name + "@" + hmitree_paths[page_index]);
   522         : page_name + "@" + hmitree_paths[page_index]);
   502 
   523 
   570         group.transform.baseVal.appendItem(transform);
   591         group.transform.baseVal.appendItem(transform);
   571         ["x", "y"].forEach((axis) => group.removeAttribute("svghmi_"+axis+"_offset"));
   592         ["x", "y"].forEach((axis) => group.removeAttribute("svghmi_"+axis+"_offset"));
   572     });
   593     });
   573 }
   594 }
   574 
   595 
       
   596 // prepare SVG
       
   597 apply_reference_frames();
       
   598 init_widgets();
       
   599 detach_detachables();
       
   600 
       
   601 // show main page
       
   602 switch_page(default_page);
       
   603 
       
   604 // initialize screensaver
       
   605 reset_screensaver_timer();
       
   606 
       
   607 var reconnect_delay = 0;
       
   608 var periodic_reconnect_timer;
       
   609 
   575 // Once connection established
   610 // Once connection established
   576 ws.onopen = function (evt) {
   611 function ws_onopen(evt) {
   577     apply_reference_frames();
   612     // Work around memory leak with websocket on QtWebEngine
   578     init_widgets();
   613     // reconnect every hour to force deallocate websocket garbage
       
   614     if(window.navigator.userAgent.includes("QtWebEngine")){
       
   615         if(periodic_reconnect_timer){
       
   616             window.clearTimeout(periodic_reconnect_timer);
       
   617         }
       
   618         periodic_reconnect_timer = window.setTimeout(() => {
       
   619             ws.close();
       
   620             periodic_reconnect_timer = null;
       
   621         }, 3600000);
       
   622     }
       
   623 
       
   624     // forget subscriptions remotely
   579     send_reset();
   625     send_reset();
   580     // show main page
   626 
   581     prepare_svg();
   627     // forget earlier subscriptions locally
   582     switch_page(default_page);
   628     reset_subscription_periods();
   583 };
   629 
   584 
   630     // update PLC about subscriptions and current page
   585 ws.onclose = function (evt) {
   631     switch_page();
       
   632 
       
   633     // at first try reconnect immediately
       
   634     reconnect_delay = 1;
       
   635 };
       
   636 
       
   637 function ws_onclose(evt) {
       
   638     console.log("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+" Reload in "+reconnect_delay+"ms.");
       
   639     ws = null;
       
   640     // reconect
   586     // TODO : add visible notification while waiting for reload
   641     // TODO : add visible notification while waiting for reload
   587     console.log("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+" Reload in 10s.");
   642     window.setTimeout(create_ws, reconnect_delay);
   588     // TODO : re-enable auto reload when not in debug
   643     reconnect_delay += 500;
   589     //window.setTimeout(() => location.reload(true), 10000);
   644 };
   590     alert("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+".");
   645 
   591 
   646 var ws_url =
   592 };
   647     window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws')
       
   648     + '?mode=' + (has_watchdog ? "watchdog" : "multiclient");
       
   649 
       
   650 function create_ws(){
       
   651     ws = new WebSocket(ws_url);
       
   652     ws.binaryType = 'arraybuffer';
       
   653     ws.onmessage = ws_onmessage;
       
   654     ws.onclose = ws_onclose;
       
   655     ws.onopen = ws_onopen;
       
   656 }
       
   657 
       
   658 create_ws()
   593 
   659 
   594 const xmlns = "http://www.w3.org/2000/svg";
   660 const xmlns = "http://www.w3.org/2000/svg";
   595 var edit_callback;
   661 var edit_callback;
   596 const localtypes = {"PAGE_LOCAL":null, "HMI_LOCAL":null}
   662 const localtypes = {"PAGE_LOCAL":null, "HMI_LOCAL":null}
   597 function edit_value(path, valuetype, callback, initial) {
   663 function edit_value(path, valuetype, callback, initial) {