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], |
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, |
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) { |