8219 </xsl:text> |
8387 </xsl:text> |
8220 <xsl:text> Object.keys(hmi_widgets).forEach(function(id) { |
8388 <xsl:text> Object.keys(hmi_widgets).forEach(function(id) { |
8221 </xsl:text> |
8389 </xsl:text> |
8222 <xsl:text> let widget = hmi_widgets[id]; |
8390 <xsl:text> let widget = hmi_widgets[id]; |
8223 </xsl:text> |
8391 </xsl:text> |
8224 <xsl:text> let init = widget.init; |
8392 <xsl:text> widget.do_init(); |
8225 </xsl:text> |
8393 </xsl:text> |
8226 <xsl:text> if(typeof(init) == "function"){ |
8394 <xsl:text> }); |
8227 </xsl:text> |
8395 </xsl:text> |
8228 <xsl:text> try { |
8396 <xsl:text>}; |
8229 </xsl:text> |
8397 </xsl:text> |
8230 <xsl:text> init.call(widget); |
8398 <xsl:text> |
8231 </xsl:text> |
8399 </xsl:text> |
8232 <xsl:text> } catch(err) { |
8400 <xsl:text>// Open WebSocket to relative "/ws" address |
8233 </xsl:text> |
8401 </xsl:text> |
8234 <xsl:text> console.log(err); |
8402 <xsl:text>var has_watchdog = window.location.hash == "#watchdog"; |
|
8403 </xsl:text> |
|
8404 <xsl:text> |
|
8405 </xsl:text> |
|
8406 <xsl:text>var ws_url = |
|
8407 </xsl:text> |
|
8408 <xsl:text> window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws') |
|
8409 </xsl:text> |
|
8410 <xsl:text> + '?mode=' + (has_watchdog ? "watchdog" : "multiclient"); |
|
8411 </xsl:text> |
|
8412 <xsl:text> |
|
8413 </xsl:text> |
|
8414 <xsl:text>var ws = new WebSocket(ws_url); |
|
8415 </xsl:text> |
|
8416 <xsl:text>ws.binaryType = 'arraybuffer'; |
|
8417 </xsl:text> |
|
8418 <xsl:text> |
|
8419 </xsl:text> |
|
8420 <xsl:text>const dvgetters = { |
|
8421 </xsl:text> |
|
8422 <xsl:text> INT: (dv,offset) => [dv.getInt16(offset, true), 2], |
|
8423 </xsl:text> |
|
8424 <xsl:text> BOOL: (dv,offset) => [dv.getInt8(offset, true), 1], |
|
8425 </xsl:text> |
|
8426 <xsl:text> NODE: (dv,offset) => [dv.getInt8(offset, true), 1], |
|
8427 </xsl:text> |
|
8428 <xsl:text> REAL: (dv,offset) => [dv.getFloat32(offset, true), 4], |
|
8429 </xsl:text> |
|
8430 <xsl:text> STRING: (dv, offset) => { |
|
8431 </xsl:text> |
|
8432 <xsl:text> const size = dv.getInt8(offset); |
|
8433 </xsl:text> |
|
8434 <xsl:text> return [ |
|
8435 </xsl:text> |
|
8436 <xsl:text> String.fromCharCode.apply(null, new Uint8Array( |
|
8437 </xsl:text> |
|
8438 <xsl:text> dv.buffer, /* original buffer */ |
|
8439 </xsl:text> |
|
8440 <xsl:text> offset + 1, /* string starts after size*/ |
|
8441 </xsl:text> |
|
8442 <xsl:text> size /* size of string */ |
|
8443 </xsl:text> |
|
8444 <xsl:text> )), size + 1]; /* total increment */ |
|
8445 </xsl:text> |
|
8446 <xsl:text> } |
|
8447 </xsl:text> |
|
8448 <xsl:text>}; |
|
8449 </xsl:text> |
|
8450 <xsl:text> |
|
8451 </xsl:text> |
|
8452 <xsl:text>// Apply updates recieved through ws.onmessage to subscribed widgets |
|
8453 </xsl:text> |
|
8454 <xsl:text>function apply_updates() { |
|
8455 </xsl:text> |
|
8456 <xsl:text> updates.forEach((value, index) => { |
|
8457 </xsl:text> |
|
8458 <xsl:text> dispatch_value(index, value); |
|
8459 </xsl:text> |
|
8460 <xsl:text> }); |
|
8461 </xsl:text> |
|
8462 <xsl:text> updates.clear(); |
|
8463 </xsl:text> |
|
8464 <xsl:text>} |
|
8465 </xsl:text> |
|
8466 <xsl:text> |
|
8467 </xsl:text> |
|
8468 <xsl:text>// Called on requestAnimationFrame, modifies DOM |
|
8469 </xsl:text> |
|
8470 <xsl:text>var requestAnimationFrameID = null; |
|
8471 </xsl:text> |
|
8472 <xsl:text>function animate() { |
|
8473 </xsl:text> |
|
8474 <xsl:text> // Do the page swith if any one pending |
|
8475 </xsl:text> |
|
8476 <xsl:text> if(current_subscribed_page != current_visible_page){ |
|
8477 </xsl:text> |
|
8478 <xsl:text> switch_visible_page(current_subscribed_page); |
|
8479 </xsl:text> |
|
8480 <xsl:text> } |
|
8481 </xsl:text> |
|
8482 <xsl:text> |
|
8483 </xsl:text> |
|
8484 <xsl:text> while(widget = need_cache_apply.pop()){ |
|
8485 </xsl:text> |
|
8486 <xsl:text> widget.apply_cache(); |
|
8487 </xsl:text> |
|
8488 <xsl:text> } |
|
8489 </xsl:text> |
|
8490 <xsl:text> |
|
8491 </xsl:text> |
|
8492 <xsl:text> if(jumps_need_update) update_jumps(); |
|
8493 </xsl:text> |
|
8494 <xsl:text> |
|
8495 </xsl:text> |
|
8496 <xsl:text> apply_updates(); |
|
8497 </xsl:text> |
|
8498 <xsl:text> |
|
8499 </xsl:text> |
|
8500 <xsl:text> pending_widget_animates.forEach(widget => widget._animate()); |
|
8501 </xsl:text> |
|
8502 <xsl:text> pending_widget_animates = []; |
|
8503 </xsl:text> |
|
8504 <xsl:text> |
|
8505 </xsl:text> |
|
8506 <xsl:text> requestAnimationFrameID = null; |
|
8507 </xsl:text> |
|
8508 <xsl:text>} |
|
8509 </xsl:text> |
|
8510 <xsl:text> |
|
8511 </xsl:text> |
|
8512 <xsl:text>function requestHMIAnimation() { |
|
8513 </xsl:text> |
|
8514 <xsl:text> if(requestAnimationFrameID == null){ |
|
8515 </xsl:text> |
|
8516 <xsl:text> requestAnimationFrameID = window.requestAnimationFrame(animate); |
|
8517 </xsl:text> |
|
8518 <xsl:text> } |
|
8519 </xsl:text> |
|
8520 <xsl:text>} |
|
8521 </xsl:text> |
|
8522 <xsl:text> |
|
8523 </xsl:text> |
|
8524 <xsl:text>// Message reception handler |
|
8525 </xsl:text> |
|
8526 <xsl:text>// Hash is verified and HMI values updates resulting from binary parsing |
|
8527 </xsl:text> |
|
8528 <xsl:text>// are stored until browser can compute next frame, DOM is left untouched |
|
8529 </xsl:text> |
|
8530 <xsl:text>ws.onmessage = function (evt) { |
|
8531 </xsl:text> |
|
8532 <xsl:text> |
|
8533 </xsl:text> |
|
8534 <xsl:text> let data = evt.data; |
|
8535 </xsl:text> |
|
8536 <xsl:text> let dv = new DataView(data); |
|
8537 </xsl:text> |
|
8538 <xsl:text> let i = 0; |
|
8539 </xsl:text> |
|
8540 <xsl:text> try { |
|
8541 </xsl:text> |
|
8542 <xsl:text> for(let hash_int of hmi_hash) { |
|
8543 </xsl:text> |
|
8544 <xsl:text> if(hash_int != dv.getUint8(i)){ |
|
8545 </xsl:text> |
|
8546 <xsl:text> throw new Error("Hash doesn't match"); |
|
8547 </xsl:text> |
|
8548 <xsl:text> }; |
|
8549 </xsl:text> |
|
8550 <xsl:text> i++; |
|
8551 </xsl:text> |
|
8552 <xsl:text> }; |
|
8553 </xsl:text> |
|
8554 <xsl:text> |
|
8555 </xsl:text> |
|
8556 <xsl:text> while(i < data.byteLength){ |
|
8557 </xsl:text> |
|
8558 <xsl:text> let index = dv.getUint32(i, true); |
|
8559 </xsl:text> |
|
8560 <xsl:text> i += 4; |
|
8561 </xsl:text> |
|
8562 <xsl:text> let iectype = hmitree_types[index]; |
|
8563 </xsl:text> |
|
8564 <xsl:text> if(iectype != undefined){ |
|
8565 </xsl:text> |
|
8566 <xsl:text> let dvgetter = dvgetters[iectype]; |
|
8567 </xsl:text> |
|
8568 <xsl:text> let [value, bytesize] = dvgetter(dv,i); |
|
8569 </xsl:text> |
|
8570 <xsl:text> updates.set(index, value); |
|
8571 </xsl:text> |
|
8572 <xsl:text> i += bytesize; |
|
8573 </xsl:text> |
|
8574 <xsl:text> } else { |
|
8575 </xsl:text> |
|
8576 <xsl:text> throw new Error("Unknown index "+index); |
8235 </xsl:text> |
8577 </xsl:text> |
8236 <xsl:text> } |
8578 <xsl:text> } |
8237 </xsl:text> |
8579 </xsl:text> |
|
8580 <xsl:text> }; |
|
8581 </xsl:text> |
|
8582 <xsl:text> // register for rendering on next frame, since there are updates |
|
8583 </xsl:text> |
|
8584 <xsl:text> requestHMIAnimation(); |
|
8585 </xsl:text> |
|
8586 <xsl:text> } catch(err) { |
|
8587 </xsl:text> |
|
8588 <xsl:text> // 1003 is for "Unsupported Data" |
|
8589 </xsl:text> |
|
8590 <xsl:text> // ws.close(1003, err.message); |
|
8591 </xsl:text> |
|
8592 <xsl:text> |
|
8593 </xsl:text> |
|
8594 <xsl:text> // TODO : remove debug alert ? |
|
8595 </xsl:text> |
|
8596 <xsl:text> alert("Error : "+err.message+"\nHMI will be reloaded."); |
|
8597 </xsl:text> |
|
8598 <xsl:text> |
|
8599 </xsl:text> |
|
8600 <xsl:text> // force reload ignoring cache |
|
8601 </xsl:text> |
|
8602 <xsl:text> location.reload(true); |
|
8603 </xsl:text> |
|
8604 <xsl:text> } |
|
8605 </xsl:text> |
|
8606 <xsl:text>}; |
|
8607 </xsl:text> |
|
8608 <xsl:text> |
|
8609 </xsl:text> |
|
8610 <xsl:text>hmi_hash_u8 = new Uint8Array(hmi_hash); |
|
8611 </xsl:text> |
|
8612 <xsl:text> |
|
8613 </xsl:text> |
|
8614 <xsl:text>function send_blob(data) { |
|
8615 </xsl:text> |
|
8616 <xsl:text> if(data.length > 0) { |
|
8617 </xsl:text> |
|
8618 <xsl:text> ws.send(new Blob([hmi_hash_u8].concat(data))); |
|
8619 </xsl:text> |
|
8620 <xsl:text> }; |
|
8621 </xsl:text> |
|
8622 <xsl:text>}; |
|
8623 </xsl:text> |
|
8624 <xsl:text> |
|
8625 </xsl:text> |
|
8626 <xsl:text>const typedarray_types = { |
|
8627 </xsl:text> |
|
8628 <xsl:text> INT: (number) => new Int16Array([number]), |
|
8629 </xsl:text> |
|
8630 <xsl:text> BOOL: (truth) => new Int16Array([truth]), |
|
8631 </xsl:text> |
|
8632 <xsl:text> NODE: (truth) => new Int16Array([truth]), |
|
8633 </xsl:text> |
|
8634 <xsl:text> REAL: (number) => new Float32Array([number]), |
|
8635 </xsl:text> |
|
8636 <xsl:text> STRING: (str) => { |
|
8637 </xsl:text> |
|
8638 <xsl:text> // beremiz default string max size is 128 |
|
8639 </xsl:text> |
|
8640 <xsl:text> str = str.slice(0,128); |
|
8641 </xsl:text> |
|
8642 <xsl:text> binary = new Uint8Array(str.length + 1); |
|
8643 </xsl:text> |
|
8644 <xsl:text> binary[0] = str.length; |
|
8645 </xsl:text> |
|
8646 <xsl:text> for(let i = 0; i < str.length; i++){ |
|
8647 </xsl:text> |
|
8648 <xsl:text> binary[i+1] = str.charCodeAt(i); |
|
8649 </xsl:text> |
8238 <xsl:text> } |
8650 <xsl:text> } |
8239 </xsl:text> |
8651 </xsl:text> |
8240 <xsl:text> if(widget.forced_frequency !== undefined) |
8652 <xsl:text> return binary; |
8241 </xsl:text> |
8653 </xsl:text> |
8242 <xsl:text> widget.frequency = widget.forced_frequency; |
8654 <xsl:text> } |
|
8655 </xsl:text> |
|
8656 <xsl:text> /* TODO */ |
|
8657 </xsl:text> |
|
8658 <xsl:text>}; |
|
8659 </xsl:text> |
|
8660 <xsl:text> |
|
8661 </xsl:text> |
|
8662 <xsl:text>function send_reset() { |
|
8663 </xsl:text> |
|
8664 <xsl:text> send_blob(new Uint8Array([1])); /* reset = 1 */ |
|
8665 </xsl:text> |
|
8666 <xsl:text>}; |
|
8667 </xsl:text> |
|
8668 <xsl:text> |
|
8669 </xsl:text> |
|
8670 <xsl:text>var subscriptions = []; |
|
8671 </xsl:text> |
|
8672 <xsl:text> |
|
8673 </xsl:text> |
|
8674 <xsl:text>function subscribers(index) { |
|
8675 </xsl:text> |
|
8676 <xsl:text> let entry = subscriptions[index]; |
|
8677 </xsl:text> |
|
8678 <xsl:text> let res; |
|
8679 </xsl:text> |
|
8680 <xsl:text> if(entry == undefined){ |
|
8681 </xsl:text> |
|
8682 <xsl:text> res = new Set(); |
|
8683 </xsl:text> |
|
8684 <xsl:text> subscriptions[index] = [res,0]; |
|
8685 </xsl:text> |
|
8686 <xsl:text> }else{ |
|
8687 </xsl:text> |
|
8688 <xsl:text> [res, _ign] = entry; |
|
8689 </xsl:text> |
|
8690 <xsl:text> } |
|
8691 </xsl:text> |
|
8692 <xsl:text> return res |
|
8693 </xsl:text> |
|
8694 <xsl:text>} |
|
8695 </xsl:text> |
|
8696 <xsl:text> |
|
8697 </xsl:text> |
|
8698 <xsl:text>function get_subscription_period(index) { |
|
8699 </xsl:text> |
|
8700 <xsl:text> let entry = subscriptions[index]; |
|
8701 </xsl:text> |
|
8702 <xsl:text> if(entry == undefined) |
|
8703 </xsl:text> |
|
8704 <xsl:text> return 0; |
|
8705 </xsl:text> |
|
8706 <xsl:text> let [_ign, period] = entry; |
|
8707 </xsl:text> |
|
8708 <xsl:text> return period; |
|
8709 </xsl:text> |
|
8710 <xsl:text>} |
|
8711 </xsl:text> |
|
8712 <xsl:text> |
|
8713 </xsl:text> |
|
8714 <xsl:text>function set_subscription_period(index, period) { |
|
8715 </xsl:text> |
|
8716 <xsl:text> let entry = subscriptions[index]; |
|
8717 </xsl:text> |
|
8718 <xsl:text> if(entry == undefined){ |
|
8719 </xsl:text> |
|
8720 <xsl:text> subscriptions[index] = [new Set(), period]; |
|
8721 </xsl:text> |
|
8722 <xsl:text> } else { |
|
8723 </xsl:text> |
|
8724 <xsl:text> entry[1] = period; |
|
8725 </xsl:text> |
|
8726 <xsl:text> } |
|
8727 </xsl:text> |
|
8728 <xsl:text>} |
|
8729 </xsl:text> |
|
8730 <xsl:text> |
|
8731 </xsl:text> |
|
8732 <xsl:text>if(has_watchdog){ |
|
8733 </xsl:text> |
|
8734 <xsl:text> // artificially subscribe the watchdog widget to "/heartbeat" hmi variable |
|
8735 </xsl:text> |
|
8736 <xsl:text> // Since dispatch directly calls change_hmi_value, |
|
8737 </xsl:text> |
|
8738 <xsl:text> // PLC will periodically send variable at given frequency |
|
8739 </xsl:text> |
|
8740 <xsl:text> subscribers(heartbeat_index).add({ |
|
8741 </xsl:text> |
|
8742 <xsl:text> /* type: "Watchdog", */ |
|
8743 </xsl:text> |
|
8744 <xsl:text> frequency: 1, |
|
8745 </xsl:text> |
|
8746 <xsl:text> indexes: [heartbeat_index], |
|
8747 </xsl:text> |
|
8748 <xsl:text> new_hmi_value: function(index, value, oldval) { |
|
8749 </xsl:text> |
|
8750 <xsl:text> apply_hmi_value(heartbeat_index, value+1); |
|
8751 </xsl:text> |
|
8752 <xsl:text> } |
8243 </xsl:text> |
8753 </xsl:text> |
8244 <xsl:text> }); |
8754 <xsl:text> }); |
8245 </xsl:text> |
8755 </xsl:text> |
8246 <xsl:text>}; |
8756 <xsl:text>} |
8247 </xsl:text> |
8757 </xsl:text> |
8248 <xsl:text> |
8758 <xsl:text> |
8249 </xsl:text> |
8759 </xsl:text> |
8250 <xsl:text>// Open WebSocket to relative "/ws" address |
8760 <xsl:text>// subscribe to per instance current page hmi variable |
8251 </xsl:text> |
8761 </xsl:text> |
8252 <xsl:text>var has_watchdog = window.location.hash == "#watchdog"; |
8762 <xsl:text>// PLC must prefix page name with "!" for page switch to happen |
8253 </xsl:text> |
8763 </xsl:text> |
8254 <xsl:text> |
8764 <xsl:text>subscribers(current_page_var_index).add({ |
8255 </xsl:text> |
8765 </xsl:text> |
8256 <xsl:text>var ws_url = |
8766 <xsl:text> frequency: 1, |
8257 </xsl:text> |
8767 </xsl:text> |
8258 <xsl:text> window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws') |
8768 <xsl:text> indexes: [current_page_var_index], |
8259 </xsl:text> |
8769 </xsl:text> |
8260 <xsl:text> + '?mode=' + (has_watchdog ? "watchdog" : "multiclient"); |
8770 <xsl:text> new_hmi_value: function(index, value, oldval) { |
8261 </xsl:text> |
8771 </xsl:text> |
8262 <xsl:text> |
8772 <xsl:text> if(value.startsWith("!")) |
8263 </xsl:text> |
8773 </xsl:text> |
8264 <xsl:text>var ws = new WebSocket(ws_url); |
8774 <xsl:text> switch_page(value.slice(1)); |
8265 </xsl:text> |
|
8266 <xsl:text>ws.binaryType = 'arraybuffer'; |
|
8267 </xsl:text> |
|
8268 <xsl:text> |
|
8269 </xsl:text> |
|
8270 <xsl:text>const dvgetters = { |
|
8271 </xsl:text> |
|
8272 <xsl:text> INT: (dv,offset) => [dv.getInt16(offset, true), 2], |
|
8273 </xsl:text> |
|
8274 <xsl:text> BOOL: (dv,offset) => [dv.getInt8(offset, true), 1], |
|
8275 </xsl:text> |
|
8276 <xsl:text> NODE: (dv,offset) => [dv.getInt8(offset, true), 1], |
|
8277 </xsl:text> |
|
8278 <xsl:text> REAL: (dv,offset) => [dv.getFloat32(offset, true), 4], |
|
8279 </xsl:text> |
|
8280 <xsl:text> STRING: (dv, offset) => { |
|
8281 </xsl:text> |
|
8282 <xsl:text> const size = dv.getInt8(offset); |
|
8283 </xsl:text> |
|
8284 <xsl:text> return [ |
|
8285 </xsl:text> |
|
8286 <xsl:text> String.fromCharCode.apply(null, new Uint8Array( |
|
8287 </xsl:text> |
|
8288 <xsl:text> dv.buffer, /* original buffer */ |
|
8289 </xsl:text> |
|
8290 <xsl:text> offset + 1, /* string starts after size*/ |
|
8291 </xsl:text> |
|
8292 <xsl:text> size /* size of string */ |
|
8293 </xsl:text> |
|
8294 <xsl:text> )), size + 1]; /* total increment */ |
|
8295 </xsl:text> |
8775 </xsl:text> |
8296 <xsl:text> } |
8776 <xsl:text> } |
8297 </xsl:text> |
8777 </xsl:text> |
8298 <xsl:text>}; |
8778 <xsl:text>}); |
8299 </xsl:text> |
8779 </xsl:text> |
8300 <xsl:text> |
8780 <xsl:text> |
8301 </xsl:text> |
8781 </xsl:text> |
8302 <xsl:text>// Apply updates recieved through ws.onmessage to subscribed widgets |
8782 <xsl:text>function svg_text_to_multiline(elt) { |
8303 </xsl:text> |
8783 </xsl:text> |
8304 <xsl:text>function apply_updates() { |
8784 <xsl:text> return(Array.prototype.map.call(elt.children, x=>x.textContent).join("\n")); |
8305 </xsl:text> |
|
8306 <xsl:text> updates.forEach((value, index) => { |
|
8307 </xsl:text> |
|
8308 <xsl:text> dispatch_value(index, value); |
|
8309 </xsl:text> |
|
8310 <xsl:text> }); |
|
8311 </xsl:text> |
|
8312 <xsl:text> updates.clear(); |
|
8313 </xsl:text> |
8785 </xsl:text> |
8314 <xsl:text>} |
8786 <xsl:text>} |
8315 </xsl:text> |
8787 </xsl:text> |
8316 <xsl:text> |
8788 <xsl:text> |
8317 </xsl:text> |
8789 </xsl:text> |
8318 <xsl:text>// Called on requestAnimationFrame, modifies DOM |
8790 <xsl:text>function multiline_to_svg_text(elt, str) { |
8319 </xsl:text> |
8791 </xsl:text> |
8320 <xsl:text>var requestAnimationFrameID = null; |
8792 <xsl:text> str.split('\n').map((line,i) => {elt.children[i].textContent = line;}); |
8321 </xsl:text> |
8793 </xsl:text> |
8322 <xsl:text>function animate() { |
8794 <xsl:text>} |
8323 </xsl:text> |
8795 </xsl:text> |
8324 <xsl:text> // Do the page swith if any one pending |
8796 <xsl:text> |
8325 </xsl:text> |
8797 </xsl:text> |
8326 <xsl:text> if(current_subscribed_page != current_visible_page){ |
8798 <xsl:text>function switch_langnum(langnum) { |
8327 </xsl:text> |
8799 </xsl:text> |
8328 <xsl:text> switch_visible_page(current_subscribed_page); |
8800 <xsl:text> langnum = Math.max(0, Math.min(langs.length - 1, langnum)); |
|
8801 </xsl:text> |
|
8802 <xsl:text> |
|
8803 </xsl:text> |
|
8804 <xsl:text> for (let translation of translations) { |
|
8805 </xsl:text> |
|
8806 <xsl:text> let [objs, msgs] = translation; |
|
8807 </xsl:text> |
|
8808 <xsl:text> let msg = msgs[langnum]; |
|
8809 </xsl:text> |
|
8810 <xsl:text> for (let obj of objs) { |
|
8811 </xsl:text> |
|
8812 <xsl:text> multiline_to_svg_text(obj, msg); |
|
8813 </xsl:text> |
|
8814 <xsl:text> obj.setAttribute("lang",langnum); |
|
8815 </xsl:text> |
|
8816 <xsl:text> } |
8329 </xsl:text> |
8817 </xsl:text> |
8330 <xsl:text> } |
8818 <xsl:text> } |
8331 </xsl:text> |
8819 </xsl:text> |
8332 <xsl:text> |
8820 <xsl:text> return langnum; |
8333 </xsl:text> |
8821 </xsl:text> |
8334 <xsl:text> while(widget = need_cache_apply.pop()){ |
8822 <xsl:text>} |
8335 </xsl:text> |
8823 </xsl:text> |
8336 <xsl:text> widget.apply_cache(); |
8824 <xsl:text> |
|
8825 </xsl:text> |
|
8826 <xsl:text>// backup original texts |
|
8827 </xsl:text> |
|
8828 <xsl:text>for (let translation of translations) { |
|
8829 </xsl:text> |
|
8830 <xsl:text> let [objs, msgs] = translation; |
|
8831 </xsl:text> |
|
8832 <xsl:text> msgs.unshift(svg_text_to_multiline(objs[0])); |
|
8833 </xsl:text> |
|
8834 <xsl:text>} |
|
8835 </xsl:text> |
|
8836 <xsl:text> |
|
8837 </xsl:text> |
|
8838 <xsl:text>var lang_local_index = hmi_local_index("lang"); |
|
8839 </xsl:text> |
|
8840 <xsl:text>var langcode_local_index = hmi_local_index("lang_code"); |
|
8841 </xsl:text> |
|
8842 <xsl:text>var langname_local_index = hmi_local_index("lang_name"); |
|
8843 </xsl:text> |
|
8844 <xsl:text>subscribers(lang_local_index).add({ |
|
8845 </xsl:text> |
|
8846 <xsl:text> indexes: [lang_local_index], |
|
8847 </xsl:text> |
|
8848 <xsl:text> new_hmi_value: function(index, value, oldval) { |
|
8849 </xsl:text> |
|
8850 <xsl:text> let current_lang = switch_langnum(value); |
|
8851 </xsl:text> |
|
8852 <xsl:text> let [langname,langcode] = langs[current_lang]; |
|
8853 </xsl:text> |
|
8854 <xsl:text> apply_hmi_value(langcode_local_index, langcode); |
|
8855 </xsl:text> |
|
8856 <xsl:text> apply_hmi_value(langname_local_index, langname); |
|
8857 </xsl:text> |
|
8858 <xsl:text> switch_page(); |
8337 </xsl:text> |
8859 </xsl:text> |
8338 <xsl:text> } |
8860 <xsl:text> } |
8339 </xsl:text> |
8861 </xsl:text> |
8340 <xsl:text> |
8862 <xsl:text>}); |
8341 </xsl:text> |
8863 </xsl:text> |
8342 <xsl:text> if(jumps_need_update) update_jumps(); |
8864 <xsl:text> |
8343 </xsl:text> |
8865 </xsl:text> |
8344 <xsl:text> |
8866 <xsl:text>// returns en_US, fr_FR or en_UK depending on selected language |
8345 </xsl:text> |
8867 </xsl:text> |
8346 <xsl:text> apply_updates(); |
8868 <xsl:text>function get_current_lang_code(){ |
8347 </xsl:text> |
8869 </xsl:text> |
8348 <xsl:text> |
8870 <xsl:text> return cache[langcode_local_index]; |
8349 </xsl:text> |
|
8350 <xsl:text> pending_widget_animates.forEach(widget => widget._animate()); |
|
8351 </xsl:text> |
|
8352 <xsl:text> pending_widget_animates = []; |
|
8353 </xsl:text> |
|
8354 <xsl:text> |
|
8355 </xsl:text> |
|
8356 <xsl:text> requestAnimationFrameID = null; |
|
8357 </xsl:text> |
8871 </xsl:text> |
8358 <xsl:text>} |
8872 <xsl:text>} |
8359 </xsl:text> |
|
8360 <xsl:text> |
|
8361 </xsl:text> |
|
8362 <xsl:text>function requestHMIAnimation() { |
|
8363 </xsl:text> |
|
8364 <xsl:text> if(requestAnimationFrameID == null){ |
|
8365 </xsl:text> |
|
8366 <xsl:text> requestAnimationFrameID = window.requestAnimationFrame(animate); |
|
8367 </xsl:text> |
|
8368 <xsl:text> } |
|
8369 </xsl:text> |
|
8370 <xsl:text>} |
|
8371 </xsl:text> |
|
8372 <xsl:text> |
|
8373 </xsl:text> |
|
8374 <xsl:text>// Message reception handler |
|
8375 </xsl:text> |
|
8376 <xsl:text>// Hash is verified and HMI values updates resulting from binary parsing |
|
8377 </xsl:text> |
|
8378 <xsl:text>// are stored until browser can compute next frame, DOM is left untouched |
|
8379 </xsl:text> |
|
8380 <xsl:text>ws.onmessage = function (evt) { |
|
8381 </xsl:text> |
|
8382 <xsl:text> |
|
8383 </xsl:text> |
|
8384 <xsl:text> let data = evt.data; |
|
8385 </xsl:text> |
|
8386 <xsl:text> let dv = new DataView(data); |
|
8387 </xsl:text> |
|
8388 <xsl:text> let i = 0; |
|
8389 </xsl:text> |
|
8390 <xsl:text> try { |
|
8391 </xsl:text> |
|
8392 <xsl:text> for(let hash_int of hmi_hash) { |
|
8393 </xsl:text> |
|
8394 <xsl:text> if(hash_int != dv.getUint8(i)){ |
|
8395 </xsl:text> |
|
8396 <xsl:text> throw new Error("Hash doesn't match"); |
|
8397 </xsl:text> |
|
8398 <xsl:text> }; |
|
8399 </xsl:text> |
|
8400 <xsl:text> i++; |
|
8401 </xsl:text> |
|
8402 <xsl:text> }; |
|
8403 </xsl:text> |
|
8404 <xsl:text> |
|
8405 </xsl:text> |
|
8406 <xsl:text> while(i < data.byteLength){ |
|
8407 </xsl:text> |
|
8408 <xsl:text> let index = dv.getUint32(i, true); |
|
8409 </xsl:text> |
|
8410 <xsl:text> i += 4; |
|
8411 </xsl:text> |
|
8412 <xsl:text> let iectype = hmitree_types[index]; |
|
8413 </xsl:text> |
|
8414 <xsl:text> if(iectype != undefined){ |
|
8415 </xsl:text> |
|
8416 <xsl:text> let dvgetter = dvgetters[iectype]; |
|
8417 </xsl:text> |
|
8418 <xsl:text> let [value, bytesize] = dvgetter(dv,i); |
|
8419 </xsl:text> |
|
8420 <xsl:text> updates.set(index, value); |
|
8421 </xsl:text> |
|
8422 <xsl:text> i += bytesize; |
|
8423 </xsl:text> |
|
8424 <xsl:text> } else { |
|
8425 </xsl:text> |
|
8426 <xsl:text> throw new Error("Unknown index "+index); |
|
8427 </xsl:text> |
|
8428 <xsl:text> } |
|
8429 </xsl:text> |
|
8430 <xsl:text> }; |
|
8431 </xsl:text> |
|
8432 <xsl:text> // register for rendering on next frame, since there are updates |
|
8433 </xsl:text> |
|
8434 <xsl:text> requestHMIAnimation(); |
|
8435 </xsl:text> |
|
8436 <xsl:text> } catch(err) { |
|
8437 </xsl:text> |
|
8438 <xsl:text> // 1003 is for "Unsupported Data" |
|
8439 </xsl:text> |
|
8440 <xsl:text> // ws.close(1003, err.message); |
|
8441 </xsl:text> |
|
8442 <xsl:text> |
|
8443 </xsl:text> |
|
8444 <xsl:text> // TODO : remove debug alert ? |
|
8445 </xsl:text> |
|
8446 <xsl:text> alert("Error : "+err.message+"\nHMI will be reloaded."); |
|
8447 </xsl:text> |
|
8448 <xsl:text> |
|
8449 </xsl:text> |
|
8450 <xsl:text> // force reload ignoring cache |
|
8451 </xsl:text> |
|
8452 <xsl:text> location.reload(true); |
|
8453 </xsl:text> |
|
8454 <xsl:text> } |
|
8455 </xsl:text> |
|
8456 <xsl:text>}; |
|
8457 </xsl:text> |
|
8458 <xsl:text> |
|
8459 </xsl:text> |
|
8460 <xsl:text>hmi_hash_u8 = new Uint8Array(hmi_hash); |
|
8461 </xsl:text> |
|
8462 <xsl:text> |
|
8463 </xsl:text> |
|
8464 <xsl:text>function send_blob(data) { |
|
8465 </xsl:text> |
|
8466 <xsl:text> if(data.length > 0) { |
|
8467 </xsl:text> |
|
8468 <xsl:text> ws.send(new Blob([hmi_hash_u8].concat(data))); |
|
8469 </xsl:text> |
|
8470 <xsl:text> }; |
|
8471 </xsl:text> |
|
8472 <xsl:text>}; |
|
8473 </xsl:text> |
|
8474 <xsl:text> |
|
8475 </xsl:text> |
|
8476 <xsl:text>const typedarray_types = { |
|
8477 </xsl:text> |
|
8478 <xsl:text> INT: (number) => new Int16Array([number]), |
|
8479 </xsl:text> |
|
8480 <xsl:text> BOOL: (truth) => new Int16Array([truth]), |
|
8481 </xsl:text> |
|
8482 <xsl:text> NODE: (truth) => new Int16Array([truth]), |
|
8483 </xsl:text> |
|
8484 <xsl:text> REAL: (number) => new Float32Array([number]), |
|
8485 </xsl:text> |
|
8486 <xsl:text> STRING: (str) => { |
|
8487 </xsl:text> |
|
8488 <xsl:text> // beremiz default string max size is 128 |
|
8489 </xsl:text> |
|
8490 <xsl:text> str = str.slice(0,128); |
|
8491 </xsl:text> |
|
8492 <xsl:text> binary = new Uint8Array(str.length + 1); |
|
8493 </xsl:text> |
|
8494 <xsl:text> binary[0] = str.length; |
|
8495 </xsl:text> |
|
8496 <xsl:text> for(let i = 0; i < str.length; i++){ |
|
8497 </xsl:text> |
|
8498 <xsl:text> binary[i+1] = str.charCodeAt(i); |
|
8499 </xsl:text> |
|
8500 <xsl:text> } |
|
8501 </xsl:text> |
|
8502 <xsl:text> return binary; |
|
8503 </xsl:text> |
|
8504 <xsl:text> } |
|
8505 </xsl:text> |
|
8506 <xsl:text> /* TODO */ |
|
8507 </xsl:text> |
|
8508 <xsl:text>}; |
|
8509 </xsl:text> |
|
8510 <xsl:text> |
|
8511 </xsl:text> |
|
8512 <xsl:text>function send_reset() { |
|
8513 </xsl:text> |
|
8514 <xsl:text> send_blob(new Uint8Array([1])); /* reset = 1 */ |
|
8515 </xsl:text> |
|
8516 <xsl:text>}; |
|
8517 </xsl:text> |
|
8518 <xsl:text> |
|
8519 </xsl:text> |
|
8520 <xsl:text>var subscriptions = []; |
|
8521 </xsl:text> |
|
8522 <xsl:text> |
|
8523 </xsl:text> |
|
8524 <xsl:text>function subscribers(index) { |
|
8525 </xsl:text> |
|
8526 <xsl:text> let entry = subscriptions[index]; |
|
8527 </xsl:text> |
|
8528 <xsl:text> let res; |
|
8529 </xsl:text> |
|
8530 <xsl:text> if(entry == undefined){ |
|
8531 </xsl:text> |
|
8532 <xsl:text> res = new Set(); |
|
8533 </xsl:text> |
|
8534 <xsl:text> subscriptions[index] = [res,0]; |
|
8535 </xsl:text> |
|
8536 <xsl:text> }else{ |
|
8537 </xsl:text> |
|
8538 <xsl:text> [res, _ign] = entry; |
|
8539 </xsl:text> |
|
8540 <xsl:text> } |
|
8541 </xsl:text> |
|
8542 <xsl:text> return res |
|
8543 </xsl:text> |
|
8544 <xsl:text>} |
|
8545 </xsl:text> |
|
8546 <xsl:text> |
|
8547 </xsl:text> |
|
8548 <xsl:text>function get_subscription_period(index) { |
|
8549 </xsl:text> |
|
8550 <xsl:text> let entry = subscriptions[index]; |
|
8551 </xsl:text> |
|
8552 <xsl:text> if(entry == undefined) |
|
8553 </xsl:text> |
|
8554 <xsl:text> return 0; |
|
8555 </xsl:text> |
|
8556 <xsl:text> let [_ign, period] = entry; |
|
8557 </xsl:text> |
|
8558 <xsl:text> return period; |
|
8559 </xsl:text> |
|
8560 <xsl:text>} |
|
8561 </xsl:text> |
|
8562 <xsl:text> |
|
8563 </xsl:text> |
|
8564 <xsl:text>function set_subscription_period(index, period) { |
|
8565 </xsl:text> |
|
8566 <xsl:text> let entry = subscriptions[index]; |
|
8567 </xsl:text> |
|
8568 <xsl:text> if(entry == undefined){ |
|
8569 </xsl:text> |
|
8570 <xsl:text> subscriptions[index] = [new Set(), period]; |
|
8571 </xsl:text> |
|
8572 <xsl:text> } else { |
|
8573 </xsl:text> |
|
8574 <xsl:text> entry[1] = period; |
|
8575 </xsl:text> |
|
8576 <xsl:text> } |
|
8577 </xsl:text> |
|
8578 <xsl:text>} |
|
8579 </xsl:text> |
|
8580 <xsl:text> |
|
8581 </xsl:text> |
|
8582 <xsl:text>if(has_watchdog){ |
|
8583 </xsl:text> |
|
8584 <xsl:text> // artificially subscribe the watchdog widget to "/heartbeat" hmi variable |
|
8585 </xsl:text> |
|
8586 <xsl:text> // Since dispatch directly calls change_hmi_value, |
|
8587 </xsl:text> |
|
8588 <xsl:text> // PLC will periodically send variable at given frequency |
|
8589 </xsl:text> |
|
8590 <xsl:text> subscribers(heartbeat_index).add({ |
|
8591 </xsl:text> |
|
8592 <xsl:text> /* type: "Watchdog", */ |
|
8593 </xsl:text> |
|
8594 <xsl:text> frequency: 1, |
|
8595 </xsl:text> |
|
8596 <xsl:text> indexes: [heartbeat_index], |
|
8597 </xsl:text> |
|
8598 <xsl:text> new_hmi_value: function(index, value, oldval) { |
|
8599 </xsl:text> |
|
8600 <xsl:text> apply_hmi_value(heartbeat_index, value+1); |
|
8601 </xsl:text> |
|
8602 <xsl:text> } |
|
8603 </xsl:text> |
|
8604 <xsl:text> }); |
|
8605 </xsl:text> |
|
8606 <xsl:text>} |
|
8607 </xsl:text> |
|
8608 <xsl:text> |
|
8609 </xsl:text> |
|
8610 <xsl:text>// subscribe to per instance current page hmi variable |
|
8611 </xsl:text> |
|
8612 <xsl:text>// PLC must prefix page name with "!" for page switch to happen |
|
8613 </xsl:text> |
|
8614 <xsl:text>subscribers(current_page_var_index).add({ |
|
8615 </xsl:text> |
|
8616 <xsl:text> frequency: 1, |
|
8617 </xsl:text> |
|
8618 <xsl:text> indexes: [current_page_var_index], |
|
8619 </xsl:text> |
|
8620 <xsl:text> new_hmi_value: function(index, value, oldval) { |
|
8621 </xsl:text> |
|
8622 <xsl:text> if(value.startsWith("!")) |
|
8623 </xsl:text> |
|
8624 <xsl:text> switch_page(value.slice(1)); |
|
8625 </xsl:text> |
|
8626 <xsl:text> } |
|
8627 </xsl:text> |
|
8628 <xsl:text>}); |
|
8629 </xsl:text> |
|
8630 <xsl:text> |
|
8631 </xsl:text> |
|
8632 <xsl:text>function svg_text_to_multiline(elt) { |
|
8633 </xsl:text> |
|
8634 <xsl:text> return(Array.prototype.map.call(elt.children, x=>x.textContent).join("\n")); |
|
8635 </xsl:text> |
|
8636 <xsl:text>} |
|
8637 </xsl:text> |
|
8638 <xsl:text> |
|
8639 </xsl:text> |
|
8640 <xsl:text>function multiline_to_svg_text(elt, str) { |
|
8641 </xsl:text> |
|
8642 <xsl:text> str.split('\n').map((line,i) => {elt.children[i].textContent = line;}); |
|
8643 </xsl:text> |
|
8644 <xsl:text>} |
|
8645 </xsl:text> |
|
8646 <xsl:text> |
|
8647 </xsl:text> |
|
8648 <xsl:text>function switch_langnum(langnum) { |
|
8649 </xsl:text> |
|
8650 <xsl:text> langnum = Math.max(0, Math.min(langs.length - 1, langnum)); |
|
8651 </xsl:text> |
|
8652 <xsl:text> |
|
8653 </xsl:text> |
|
8654 <xsl:text> for (let translation of translations) { |
|
8655 </xsl:text> |
|
8656 <xsl:text> let [objs, msgs] = translation; |
|
8657 </xsl:text> |
|
8658 <xsl:text> let msg = msgs[langnum]; |
|
8659 </xsl:text> |
|
8660 <xsl:text> for (let obj of objs) { |
|
8661 </xsl:text> |
|
8662 <xsl:text> multiline_to_svg_text(obj, msg); |
|
8663 </xsl:text> |
|
8664 <xsl:text> obj.setAttribute("lang",langnum); |
|
8665 </xsl:text> |
|
8666 <xsl:text> } |
|
8667 </xsl:text> |
|
8668 <xsl:text> } |
|
8669 </xsl:text> |
|
8670 <xsl:text> return langnum; |
|
8671 </xsl:text> |
|
8672 <xsl:text>} |
|
8673 </xsl:text> |
|
8674 <xsl:text> |
|
8675 </xsl:text> |
|
8676 <xsl:text>// backup original texts |
|
8677 </xsl:text> |
|
8678 <xsl:text>for (let translation of translations) { |
|
8679 </xsl:text> |
|
8680 <xsl:text> let [objs, msgs] = translation; |
|
8681 </xsl:text> |
|
8682 <xsl:text> msgs.unshift(svg_text_to_multiline(objs[0])); |
|
8683 </xsl:text> |
|
8684 <xsl:text>} |
|
8685 </xsl:text> |
|
8686 <xsl:text> |
|
8687 </xsl:text> |
|
8688 <xsl:text>var lang_local_index = hmi_local_index("lang"); |
|
8689 </xsl:text> |
|
8690 <xsl:text>var langcode_local_index = hmi_local_index("lang_code"); |
|
8691 </xsl:text> |
|
8692 <xsl:text>var langname_local_index = hmi_local_index("lang_name"); |
|
8693 </xsl:text> |
|
8694 <xsl:text>subscribers(lang_local_index).add({ |
|
8695 </xsl:text> |
|
8696 <xsl:text> indexes: [lang_local_index], |
|
8697 </xsl:text> |
|
8698 <xsl:text> new_hmi_value: function(index, value, oldval) { |
|
8699 </xsl:text> |
|
8700 <xsl:text> let current_lang = switch_langnum(value); |
|
8701 </xsl:text> |
|
8702 <xsl:text> let [langname,langcode] = langs[current_lang]; |
|
8703 </xsl:text> |
|
8704 <xsl:text> apply_hmi_value(langcode_local_index, langcode); |
|
8705 </xsl:text> |
|
8706 <xsl:text> apply_hmi_value(langname_local_index, langname); |
|
8707 </xsl:text> |
|
8708 <xsl:text> switch_page(); |
|
8709 </xsl:text> |
|
8710 <xsl:text> } |
|
8711 </xsl:text> |
|
8712 <xsl:text>}); |
|
8713 </xsl:text> |
8873 </xsl:text> |
8714 <xsl:text> |
8874 <xsl:text> |
8715 </xsl:text> |
8875 </xsl:text> |
8716 <xsl:text>function setup_lang(){ |
8876 <xsl:text>function setup_lang(){ |
8717 </xsl:text> |
8877 </xsl:text> |