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 { |
197 } else { |
192 } else { |
198 entry[1] = period; |
193 entry[1] = period; |
199 } |
194 } |
200 } |
195 } |
201 |
196 |
|
197 function reset_subscription_periods() { |
|
198 for(let index in subscriptions) |
|
199 subscriptions[index][1] = 0; |
|
200 } |
|
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, |
205 // PLC will periodically send variable at given frequency |
205 // PLC will periodically send variable at given frequency |
206 subscribers(heartbeat_index).add({ |
206 subscribers(heartbeat_index).add({ |
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 function detach_detachables() { |
427 |
432 |
428 for(let eltid in detachable_elements){ |
433 for(let eltid in detachable_elements){ |
429 let [element,parent] = detachable_elements[eltid]; |
434 let [element,parent] = detachable_elements[eltid]; |
430 parent.removeChild(element); |
435 parent.removeChild(element); |
431 } |
436 } |
570 group.transform.baseVal.appendItem(transform); |
575 group.transform.baseVal.appendItem(transform); |
571 ["x", "y"].forEach((axis) => group.removeAttribute("svghmi_"+axis+"_offset")); |
576 ["x", "y"].forEach((axis) => group.removeAttribute("svghmi_"+axis+"_offset")); |
572 }); |
577 }); |
573 } |
578 } |
574 |
579 |
|
580 // prepare SVG |
|
581 apply_reference_frames(); |
|
582 init_widgets(); |
|
583 detach_detachables(); |
|
584 |
|
585 // show main page |
|
586 switch_page(default_page); |
|
587 |
|
588 var reconnect_delay = 0; |
|
589 // var periodic_reconnect_timer; |
|
590 |
575 // Once connection established |
591 // Once connection established |
576 ws.onopen = function (evt) { |
592 function ws_onopen(evt) { |
577 apply_reference_frames(); |
593 /* |
578 init_widgets(); |
594 // to force reconnect every hour |
|
595 if(periodic_reconnect_timer){ |
|
596 window.clearTimeout(periodic_reconnect_timer); |
|
597 } |
|
598 periodic_reconnect_timer = window.setTimeout(() => { |
|
599 ws.close(); |
|
600 periodic_reconnect_timer = null; |
|
601 }, 3600*1000); |
|
602 */ |
|
603 |
|
604 // forget subscriptions remotely |
579 send_reset(); |
605 send_reset(); |
580 // show main page |
606 |
581 prepare_svg(); |
607 // forget earlier subscriptions locally |
582 switch_page(default_page); |
608 reset_subscription_periods(); |
583 }; |
609 |
584 |
610 // update PLC about subscriptions and current page |
585 ws.onclose = function (evt) { |
611 switch_page(); |
|
612 |
|
613 // at first try reconnect immediately |
|
614 reconnect_delay = 1; |
|
615 }; |
|
616 |
|
617 function ws_onclose(evt) { |
|
618 console.log("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+" Reload in "+reconnect_delay+"ms."); |
|
619 ws = null; |
|
620 // reconect |
586 // TODO : add visible notification while waiting for reload |
621 // 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."); |
622 window.setTimeout(create_ws, reconnect_delay); |
588 // TODO : re-enable auto reload when not in debug |
623 reconnect_delay += 500; |
589 //window.setTimeout(() => location.reload(true), 10000); |
624 }; |
590 alert("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+"."); |
625 |
591 |
626 var ws_url = |
592 }; |
627 window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws') |
|
628 + '?mode=' + (has_watchdog ? "watchdog" : "multiclient"); |
|
629 |
|
630 function create_ws(){ |
|
631 ws = new WebSocket(ws_url); |
|
632 ws.binaryType = 'arraybuffer'; |
|
633 ws.onmessage = ws_onmessage; |
|
634 ws.onclose = ws_onclose; |
|
635 ws.onopen = ws_onopen; |
|
636 } |
|
637 |
|
638 create_ws() |
593 |
639 |
594 const xmlns = "http://www.w3.org/2000/svg"; |
640 const xmlns = "http://www.w3.org/2000/svg"; |
595 var edit_callback; |
641 var edit_callback; |
596 const localtypes = {"PAGE_LOCAL":null, "HMI_LOCAL":null} |
642 const localtypes = {"PAGE_LOCAL":null, "HMI_LOCAL":null} |
597 function edit_value(path, valuetype, callback, initial) { |
643 function edit_value(path, valuetype, callback, initial) { |