diff -r b5c6bb72bfc9 -r e0d6f5f0dcc2 svghmi/svghmi.js --- a/svghmi/svghmi.js Thu Nov 03 17:43:30 2022 +0100 +++ b/svghmi/svghmi.js Fri Nov 04 17:38:37 2022 +0100 @@ -23,13 +23,6 @@ // Open WebSocket to relative "/ws" address var has_watchdog = window.location.hash == "#watchdog"; -var ws_url = - window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws') - + '?mode=' + (has_watchdog ? "watchdog" : "multiclient"); - -var ws = new WebSocket(ws_url); -ws.binaryType = 'arraybuffer'; - const dvgetters = { INT: (dv,offset) => [dv.getInt16(offset, true), 2], BOOL: (dv,offset) => [dv.getInt8(offset, true), 1], @@ -98,7 +91,7 @@ // Message reception handler // Hash is verified and HMI values updates resulting from binary parsing // are stored until browser can compute next frame, DOM is left untouched -ws.onmessage = function (evt) { +function ws_onmessage(evt) { let data = evt.data; let dv = new DataView(data); @@ -140,16 +133,18 @@ hmi_hash_u8 = new Uint8Array(hmi_hash); +var ws = null; + function send_blob(data) { - if(data.length > 0) { + if(ws && data.length > 0) { ws.send(new Blob([hmi_hash_u8].concat(data))); }; }; const typedarray_types = { INT: (number) => new Int16Array([number]), - BOOL: (truth) => new Int16Array([truth]), - NODE: (truth) => new Int16Array([truth]), + BOOL: (truth) => new Int8Array([truth]), + NODE: (truth) => new Int8Array([truth]), REAL: (number) => new Float32Array([number]), STRING: (str) => { // beremiz default string max size is 128 @@ -199,6 +194,11 @@ } } +function reset_subscription_periods() { + for(let index in subscriptions) + subscriptions[index][1] = 0; +} + if(has_watchdog){ // artificially subscribe the watchdog widget to "/heartbeat" hmi variable // Since dispatch directly calls change_hmi_value, @@ -298,6 +298,10 @@ function update_subscriptions() { let delta = []; + if(!ws) + // dont' change subscriptions if not connected + return; + for(let index in subscriptions){ let widgets = subscribers(index); @@ -418,12 +422,26 @@ } } -function prepare_svg() { - // prevents context menu from appearing on right click and long touch - document.body.addEventListener('contextmenu', e => { - toggleFullscreen(); - e.preventDefault(); - }); +// prevents context menu from appearing on right click and long touch +document.body.addEventListener('contextmenu', e => { + toggleFullscreen(); + e.preventDefault(); +}); + +var screensaver_timer = null; +function reset_screensaver_timer() { + if(screensaver_timer){ + window.clearTimeout(screensaver_timer); + } + screensaver_timer = window.setTimeout(() => { + switch_page("ScreenSaver"); + screensaver_timer = null; + }, screensaver_delay*1000); +} +if(screensaver_delay) + document.body.addEventListener('pointerdown', reset_screensaver_timer); + +function detach_detachables() { for(let eltid in detachable_elements){ let [element,parent] = detachable_elements[eltid]; @@ -492,9 +510,12 @@ jumps_need_update = true; requestHMIAnimation(); - jump_history.push([page_name, page_index]); - if(jump_history.length > 42) - jump_history.shift(); + let [last_page_name, last_page_index] = jump_history[jump_history.length-1]; + if(last_page_name != page_name || last_page_index != page_index){ + jump_history.push([page_name, page_index]); + if(jump_history.length > 42) + jump_history.shift(); + } apply_hmi_value(current_page_var_index, page_index == undefined ? page_name @@ -572,24 +593,69 @@ }); } +// prepare SVG +apply_reference_frames(); +init_widgets(); +detach_detachables(); + +// show main page +switch_page(default_page); + +// initialize screensaver +reset_screensaver_timer(); + +var reconnect_delay = 0; +var periodic_reconnect_timer; + // Once connection established -ws.onopen = function (evt) { - apply_reference_frames(); - init_widgets(); +function ws_onopen(evt) { + // Work around memory leak with websocket on QtWebEngine + // reconnect every hour to force deallocate websocket garbage + if(window.navigator.userAgent.includes("QtWebEngine")){ + if(periodic_reconnect_timer){ + window.clearTimeout(periodic_reconnect_timer); + } + periodic_reconnect_timer = window.setTimeout(() => { + ws.close(); + periodic_reconnect_timer = null; + }, 3600000); + } + + // forget subscriptions remotely send_reset(); - // show main page - prepare_svg(); - switch_page(default_page); -}; - -ws.onclose = function (evt) { + + // forget earlier subscriptions locally + reset_subscription_periods(); + + // update PLC about subscriptions and current page + switch_page(); + + // at first try reconnect immediately + reconnect_delay = 1; +}; + +function ws_onclose(evt) { + console.log("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+" Reload in "+reconnect_delay+"ms."); + ws = null; + // reconect // TODO : add visible notification while waiting for reload - console.log("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+" Reload in 10s."); - // TODO : re-enable auto reload when not in debug - //window.setTimeout(() => location.reload(true), 10000); - alert("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+"."); - -}; + window.setTimeout(create_ws, reconnect_delay); + reconnect_delay += 500; +}; + +var ws_url = + window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws') + + '?mode=' + (has_watchdog ? "watchdog" : "multiclient"); + +function create_ws(){ + ws = new WebSocket(ws_url); + ws.binaryType = 'arraybuffer'; + ws.onmessage = ws_onmessage; + ws.onclose = ws_onclose; + ws.onopen = ws_onopen; +} + +create_ws() const xmlns = "http://www.w3.org/2000/svg"; var edit_callback;