--- a/svghmi/gen_index_xhtml.xslt Tue Aug 04 11:53:39 2020 +0200
+++ b/svghmi/gen_index_xhtml.xslt Wed Aug 05 18:49:29 2020 +0200
@@ -497,26 +497,22 @@
<xsl:text>,
</xsl:text>
</xsl:if>
- <xsl:text> relative_widgets: [
-</xsl:text>
- <xsl:for-each select="$page_relative_widgets">
- <xsl:text> hmi_widgets["</xsl:text>
+ <xsl:text> widgets: [
+</xsl:text>
+ <xsl:for-each select="$page_managed_widgets">
+ <xsl:variable name="widget_paths_relativeness">
+ <xsl:for-each select="func:widget(@id)/path">
+ <xsl:value-of select="func:is_descendant_path(@value, $desc/path/@value)"/>
+ <xsl:if test="position()!=last()">
+ <xsl:text>,</xsl:text>
+ </xsl:if>
+ </xsl:for-each>
+ </xsl:variable>
+ <xsl:text> [hmi_widgets["</xsl:text>
<xsl:value-of select="@id"/>
- <xsl:text>"]</xsl:text>
- <xsl:if test="position()!=last()">
- <xsl:text>,</xsl:text>
- </xsl:if>
- <xsl:text>
-</xsl:text>
- </xsl:for-each>
- <xsl:text> ],
-</xsl:text>
- <xsl:text> absolute_widgets: [
-</xsl:text>
- <xsl:for-each select="$page_managed_widgets[not(@id = $page_relative_widgets/@id)]">
- <xsl:text> hmi_widgets["</xsl:text>
- <xsl:value-of select="@id"/>
- <xsl:text>"]</xsl:text>
+ <xsl:text>"], [</xsl:text>
+ <xsl:value-of select="$widget_paths_relativeness"/>
+ <xsl:text>]]</xsl:text>
<xsl:if test="position()!=last()">
<xsl:text>,</xsl:text>
</xsl:if>
@@ -947,80 +943,152 @@
</xsl:text>
<xsl:text> /* remove subsribers */
</xsl:text>
+ <xsl:text> if(!this.unsubscribable)
+</xsl:text>
+ <xsl:text> for(let i = 0; i < this.indexes.length; i++) {
+</xsl:text>
+ <xsl:text> let index = this.indexes[i];
+</xsl:text>
+ <xsl:text> if(this.relativeness[i])
+</xsl:text>
+ <xsl:text> index += this.offset;
+</xsl:text>
+ <xsl:text> subscribers[index].delete(this);
+</xsl:text>
+ <xsl:text> }
+</xsl:text>
+ <xsl:text> this.offset = 0;
+</xsl:text>
+ <xsl:text> this.relativeness = undefined;
+</xsl:text>
+ <xsl:text> }
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text> sub(new_offset=0, relativeness){
+</xsl:text>
+ <xsl:text> this.offset = new_offset;
+</xsl:text>
+ <xsl:text> this.relativeness = relativeness;
+</xsl:text>
+ <xsl:text> /* add this's subsribers */
+</xsl:text>
+ <xsl:text> if(!this.unsubscribable)
+</xsl:text>
+ <xsl:text> for(let i = 0; i < this.indexes.length; i++) {
+</xsl:text>
+ <xsl:text> let index = this.indexes[i];
+</xsl:text>
+ <xsl:text> if(relativeness[i])
+</xsl:text>
+ <xsl:text> index += new_offset;
+</xsl:text>
+ <xsl:text> subscribers[index].add(this);
+</xsl:text>
+ <xsl:text> }
+</xsl:text>
+ <xsl:text> need_cache_apply.push(this);
+</xsl:text>
+ <xsl:text> }
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text> apply_cache() {
+</xsl:text>
<xsl:text> if(!this.unsubscribable) for(let index of this.indexes){
</xsl:text>
- <xsl:text> let idx = index + this.offset;
-</xsl:text>
- <xsl:text> subscribers[idx].delete(this);
+ <xsl:text> /* dispatch current cache in newly opened page widgets */
+</xsl:text>
+ <xsl:text> let realindex = index+this.offset;
+</xsl:text>
+ <xsl:text> let cached_val = cache[realindex];
+</xsl:text>
+ <xsl:text> if(cached_val != undefined)
+</xsl:text>
+ <xsl:text> this.new_hmi_value(realindex, cached_val, cached_val);
</xsl:text>
<xsl:text> }
</xsl:text>
- <xsl:text> this.offset = 0;
-</xsl:text>
<xsl:text> }
</xsl:text>
<xsl:text>
</xsl:text>
- <xsl:text> sub(new_offset=0){
-</xsl:text>
- <xsl:text> /* set the offset because relative */
-</xsl:text>
- <xsl:text> this.offset = new_offset;
-</xsl:text>
- <xsl:text> /* add this's subsribers */
-</xsl:text>
- <xsl:text> if(!this.unsubscribable) for(let index of this.indexes){
-</xsl:text>
- <xsl:text> subscribers[index + new_offset].add(this);
+ <xsl:text> get_idx(index) {
+</xsl:text>
+ <xsl:text> let orig = this.indexes[index];
+</xsl:text>
+ <xsl:text> return this.relativeness[index] ? orig + this.offset : orig;
+</xsl:text>
+ <xsl:text> }
+</xsl:text>
+ <xsl:text> change_hmi_value(index,opstr) {
+</xsl:text>
+ <xsl:text> return change_hmi_value(this.get_idx(index), opstr);
+</xsl:text>
+ <xsl:text> }
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text> apply_hmi_value(index, new_val) {
+</xsl:text>
+ <xsl:text> return apply_hmi_value(this.get_idx(0), new_val);
+</xsl:text>
+ <xsl:text> }
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text> new_hmi_value(index, value, oldval) {
+</xsl:text>
+ <xsl:text> try {
+</xsl:text>
+ <xsl:text> // TODO avoid searching, store index at sub()
+</xsl:text>
+ <xsl:text> for(let i = 0; i < this.indexes.length; i++) {
+</xsl:text>
+ <xsl:text> let refindex = this.indexes[i];
+</xsl:text>
+ <xsl:text> if(this.relativeness[i])
+</xsl:text>
+ <xsl:text> refindex += this.offset;
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text> if(index == refindex) {
+</xsl:text>
+ <xsl:text> let d = this.dispatch;
+</xsl:text>
+ <xsl:text> if(typeof(d) == "function"){
+</xsl:text>
+ <xsl:text> d.call(this, value, oldval, i);
+</xsl:text>
+ <xsl:text> }
+</xsl:text>
+ <xsl:text> else if(typeof(d) == "object"){
+</xsl:text>
+ <xsl:text> d[i].call(this, value, oldval);
+</xsl:text>
+ <xsl:text> }
+</xsl:text>
+ <xsl:text> /* else dispatch_0, ..., dispatch_n ? */
+</xsl:text>
+ <xsl:text> /*else {
+</xsl:text>
+ <xsl:text> throw new Error("Dunno how to dispatch to widget at index = " + index);
+</xsl:text>
+ <xsl:text> }*/
+</xsl:text>
+ <xsl:text> break;
+</xsl:text>
+ <xsl:text> }
+</xsl:text>
+ <xsl:text> }
+</xsl:text>
+ <xsl:text> } catch(err) {
+</xsl:text>
+ <xsl:text> console.log(err);
</xsl:text>
<xsl:text> }
</xsl:text>
- <xsl:text> need_cache_apply.push(this);
-</xsl:text>
- <xsl:text> }
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text> apply_cache() {
-</xsl:text>
- <xsl:text> if(!this.unsubscribable) for(let index of this.indexes){
-</xsl:text>
- <xsl:text> /* dispatch current cache in newly opened page widgets */
-</xsl:text>
- <xsl:text> let realindex = index+this.offset;
-</xsl:text>
- <xsl:text> let cached_val = cache[realindex];
-</xsl:text>
- <xsl:text> if(cached_val != undefined)
-</xsl:text>
- <xsl:text> dispatch_value_to_widget(this, realindex, cached_val, cached_val);
-</xsl:text>
- <xsl:text> }
-</xsl:text>
- <xsl:text> }
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text> get_idx(index) {
-</xsl:text>
- <xsl:text> let orig = this.indexes[index];
-</xsl:text>
- <xsl:text> return this.offset ? orig + this.offset : orig;
-</xsl:text>
- <xsl:text> }
-</xsl:text>
- <xsl:text> change_hmi_value(index,opstr) {
-</xsl:text>
- <xsl:text> return change_hmi_value(this.get_idx(index), opstr);
-</xsl:text>
- <xsl:text> }
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text> apply_hmi_value(index, new_val) {
-</xsl:text>
- <xsl:text> return apply_hmi_value(this.get_idx(0), new_val);
-</xsl:text>
<xsl:text> }
</xsl:text>
<xsl:text>}
@@ -1892,6 +1960,20 @@
</xsl:template>
<xsl:template mode="widget_defs" match="widget[@type='ForEach']">
<xsl:param name="hmi_element"/>
+ <xsl:if test="count(path) != 1">
+ <xsl:message terminate="yes">
+ <xsl:text>ForEach widget </xsl:text>
+ <xsl:value-of select="$hmi_element/@id"/>
+ <xsl:text> must have one HMI path given.</xsl:text>
+ </xsl:message>
+ </xsl:if>
+ <xsl:if test="count(arg) != 1">
+ <xsl:message terminate="yes">
+ <xsl:text>ForEach widget </xsl:text>
+ <xsl:value-of select="$hmi_element/@id"/>
+ <xsl:text> must have one argument given : a class name.</xsl:text>
+ </xsl:message>
+ </xsl:if>
<xsl:variable name="class" select="arg[1]/@value"/>
<xsl:variable name="base_path" select="path/@value"/>
<xsl:variable name="hmi_index_base" select="$indexed_hmitree/*[@hmipath = $base_path]"/>
@@ -1993,97 +2075,125 @@
<xsl:template mode="widget_class" match="widget[@type='ForEach']">
<xsl:text>class ForEachWidget extends Widget{
</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text> unsub_items(){
+</xsl:text>
+ <xsl:text> for(let item of this.items){
+</xsl:text>
+ <xsl:text> for(let widget of item) {
+</xsl:text>
+ <xsl:text> widget.unsub();
+</xsl:text>
+ <xsl:text> }
+</xsl:text>
+ <xsl:text> }
+</xsl:text>
+ <xsl:text> }
+</xsl:text>
+ <xsl:text>
+</xsl:text>
<xsl:text> unsub(){
</xsl:text>
- <xsl:text> for(let item of this.items){
+ <xsl:text> this.unsub_items();
+</xsl:text>
+ <xsl:text> this.offset = 0;
+</xsl:text>
+ <xsl:text> this.relativeness = undefined;
+</xsl:text>
+ <xsl:text> }
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text> sub_items(){
+</xsl:text>
+ <xsl:text> for(let i = 0; i < this.items.length; i++) {
+</xsl:text>
+ <xsl:text> let item = this.items[i];
+</xsl:text>
+ <xsl:text> let orig_item_index = this.index_pool[i];
+</xsl:text>
+ <xsl:text> let item_index = this.index_pool[i+this.item_offset];
+</xsl:text>
+ <xsl:text> let item_index_offset = item_index - orig_item_index;
+</xsl:text>
+ <xsl:text> if(this.relativeness[0])
+</xsl:text>
+ <xsl:text> item_index_offset += this.offset;
</xsl:text>
<xsl:text> for(let widget of item) {
</xsl:text>
- <xsl:text> widget.unsub();
+ <xsl:text> /* all variables of all widgets in a ForEach are all relative.
+</xsl:text>
+ <xsl:text> Really.
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text> TODO: allow absolute variables in ForEach widgets
+</xsl:text>
+ <xsl:text> */
+</xsl:text>
+ <xsl:text> widget.sub(item_index_offset, widget.indexes.map(_=>true));
</xsl:text>
<xsl:text> }
</xsl:text>
<xsl:text> }
</xsl:text>
- <xsl:text> this.offset = 0;
-</xsl:text>
<xsl:text> }
</xsl:text>
<xsl:text>
</xsl:text>
- <xsl:text> foreach_widgets_do(todo){
-</xsl:text>
- <xsl:text> for(let i = 0; i < this.items.length; i++) {
-</xsl:text>
- <xsl:text> let item = this.items[i];
-</xsl:text>
- <xsl:text> let orig_item_index = this.index_pool[i];
-</xsl:text>
- <xsl:text> let item_index = this.index_pool[i+this.item_offset];
-</xsl:text>
- <xsl:text> let item_index_offset = item_index - orig_item_index;
-</xsl:text>
- <xsl:text> for(let widget of item) {
-</xsl:text>
- <xsl:text> todo(widget).call(widget, this.offset + item_index_offset);
-</xsl:text>
- <xsl:text> }
+ <xsl:text> sub(new_offset=0, relativeness=[]){
+</xsl:text>
+ <xsl:text> this.offset = new_offset;
+</xsl:text>
+ <xsl:text> this.relativeness = relativeness;
+</xsl:text>
+ <xsl:text> this.sub_items();
+</xsl:text>
+ <xsl:text> }
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text> apply_cache() {
+</xsl:text>
+ <xsl:text> this.items.forEach(item=>item.forEach(widget=>widget.apply_cache()));
+</xsl:text>
+ <xsl:text> }
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text> on_click(opstr, evt) {
+</xsl:text>
+ <xsl:text> let new_item_offset = eval(String(this.item_offset)+opstr);
+</xsl:text>
+ <xsl:text> if(new_item_offset + this.items.length > this.index_pool.length) {
+</xsl:text>
+ <xsl:text> if(this.item_offset + this.items.length == this.index_pool.length)
+</xsl:text>
+ <xsl:text> new_item_offset = 0;
+</xsl:text>
+ <xsl:text> else
+</xsl:text>
+ <xsl:text> new_item_offset = this.index_pool.length - this.items.length;
+</xsl:text>
+ <xsl:text> } else if(new_item_offset < 0) {
+</xsl:text>
+ <xsl:text> if(this.item_offset == 0)
+</xsl:text>
+ <xsl:text> new_item_offset = this.index_pool.length - this.items.length;
+</xsl:text>
+ <xsl:text> else
+</xsl:text>
+ <xsl:text> new_item_offset = 0;
</xsl:text>
<xsl:text> }
</xsl:text>
- <xsl:text> }
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text> sub(new_offset=0){
-</xsl:text>
- <xsl:text> this.offset = new_offset;
-</xsl:text>
- <xsl:text> this.foreach_widgets_do(w=>w.sub);
-</xsl:text>
- <xsl:text> }
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text> apply_cache() {
-</xsl:text>
- <xsl:text> this.foreach_widgets_do(w=>w.apply_cache);
-</xsl:text>
- <xsl:text> }
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text> on_click(opstr, evt) {
-</xsl:text>
- <xsl:text> let new_item_offset = eval(String(this.item_offset)+opstr);
-</xsl:text>
- <xsl:text> if(new_item_offset + this.items.length > this.index_pool.length) {
-</xsl:text>
- <xsl:text> if(this.item_offset + this.items.length == this.index_pool.length)
-</xsl:text>
- <xsl:text> new_item_offset = 0;
-</xsl:text>
- <xsl:text> else
-</xsl:text>
- <xsl:text> new_item_offset = this.index_pool.length - this.items.length;
-</xsl:text>
- <xsl:text> } else if(new_item_offset < 0) {
-</xsl:text>
- <xsl:text> if(this.item_offset == 0)
-</xsl:text>
- <xsl:text> new_item_offset = this.index_pool.length - this.items.length;
-</xsl:text>
- <xsl:text> else
-</xsl:text>
- <xsl:text> new_item_offset = 0;
-</xsl:text>
- <xsl:text> }
-</xsl:text>
<xsl:text> this.item_offset = new_item_offset;
</xsl:text>
- <xsl:text> this.unsub();
-</xsl:text>
- <xsl:text> this.sub(this.offset);
+ <xsl:text> this.unsub_items();
+</xsl:text>
+ <xsl:text> this.sub_items();
</xsl:text>
<xsl:text> update_subscriptions();
</xsl:text>
@@ -3049,655 +3159,613 @@
</xsl:text>
<xsl:text>
</xsl:text>
- <xsl:text>function dispatch_value_to_widget(widget, index, value, oldval) {
+ <xsl:text>
+</xsl:text>
+ <xsl:text>function dispatch_value(index, value) {
+</xsl:text>
+ <xsl:text> let widgets = subscribers[index];
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text> let oldval = cache[index];
+</xsl:text>
+ <xsl:text> cache[index] = value;
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text> if(widgets.size > 0) {
+</xsl:text>
+ <xsl:text> for(let widget of widgets){
+</xsl:text>
+ <xsl:text> widget.new_hmi_value(index, value, oldval);
+</xsl:text>
+ <xsl:text> }
+</xsl:text>
+ <xsl:text> }
+</xsl:text>
+ <xsl:text>};
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text>function init_widgets() {
+</xsl:text>
+ <xsl:text> Object.keys(hmi_widgets).forEach(function(id) {
+</xsl:text>
+ <xsl:text> let widget = hmi_widgets[id];
+</xsl:text>
+ <xsl:text> let init = widget.init;
+</xsl:text>
+ <xsl:text> if(typeof(init) == "function"){
+</xsl:text>
+ <xsl:text> try {
+</xsl:text>
+ <xsl:text> init.call(widget);
+</xsl:text>
+ <xsl:text> } catch(err) {
+</xsl:text>
+ <xsl:text> console.log(err);
+</xsl:text>
+ <xsl:text> }
+</xsl:text>
+ <xsl:text> }
+</xsl:text>
+ <xsl:text> });
+</xsl:text>
+ <xsl:text>};
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text>// Open WebSocket to relative "/ws" address
+</xsl:text>
+ <xsl:text>var ws = new WebSocket(window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws'));
+</xsl:text>
+ <xsl:text>ws.binaryType = 'arraybuffer';
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text>const dvgetters = {
+</xsl:text>
+ <xsl:text> INT: (dv,offset) => [dv.getInt16(offset, true), 2],
+</xsl:text>
+ <xsl:text> BOOL: (dv,offset) => [dv.getInt8(offset, true), 1],
+</xsl:text>
+ <xsl:text> NODE: (dv,offset) => [dv.getInt8(offset, true), 1],
+</xsl:text>
+ <xsl:text> STRING: (dv, offset) => {
+</xsl:text>
+ <xsl:text> size = dv.getInt8(offset);
+</xsl:text>
+ <xsl:text> return [
+</xsl:text>
+ <xsl:text> String.fromCharCode.apply(null, new Uint8Array(
+</xsl:text>
+ <xsl:text> dv.buffer, /* original buffer */
+</xsl:text>
+ <xsl:text> offset + 1, /* string starts after size*/
+</xsl:text>
+ <xsl:text> size /* size of string */
+</xsl:text>
+ <xsl:text> )), size + 1]; /* total increment */
+</xsl:text>
+ <xsl:text> }
+</xsl:text>
+ <xsl:text>};
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text>// Apply updates recieved through ws.onmessage to subscribed widgets
+</xsl:text>
+ <xsl:text>function apply_updates() {
+</xsl:text>
+ <xsl:text> for(let index in updates){
+</xsl:text>
+ <xsl:text> // serving as a key, index becomes a string
+</xsl:text>
+ <xsl:text> // -> pass Number(index) instead
+</xsl:text>
+ <xsl:text> dispatch_value(Number(index), updates[index]);
+</xsl:text>
+ <xsl:text> delete updates[index];
+</xsl:text>
+ <xsl:text> }
+</xsl:text>
+ <xsl:text>}
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text>// Called on requestAnimationFrame, modifies DOM
+</xsl:text>
+ <xsl:text>var requestAnimationFrameID = null;
+</xsl:text>
+ <xsl:text>function animate() {
+</xsl:text>
+ <xsl:text> // Do the page swith if any one pending
+</xsl:text>
+ <xsl:text> if(current_subscribed_page != current_visible_page){
+</xsl:text>
+ <xsl:text> switch_visible_page(current_subscribed_page);
+</xsl:text>
+ <xsl:text> }
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text> while(widget = need_cache_apply.pop()){
+</xsl:text>
+ <xsl:text> widget.apply_cache();
+</xsl:text>
+ <xsl:text> }
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text> if(jumps_need_update) update_jumps();
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text> apply_updates();
+</xsl:text>
+ <xsl:text> requestAnimationFrameID = null;
+</xsl:text>
+ <xsl:text>}
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text>function requestHMIAnimation() {
+</xsl:text>
+ <xsl:text> if(requestAnimationFrameID == null){
+</xsl:text>
+ <xsl:text> requestAnimationFrameID = window.requestAnimationFrame(animate);
+</xsl:text>
+ <xsl:text> }
+</xsl:text>
+ <xsl:text>}
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text>// Message reception handler
+</xsl:text>
+ <xsl:text>// Hash is verified and HMI values updates resulting from binary parsing
+</xsl:text>
+ <xsl:text>// are stored until browser can compute next frame, DOM is left untouched
+</xsl:text>
+ <xsl:text>ws.onmessage = function (evt) {
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text> let data = evt.data;
+</xsl:text>
+ <xsl:text> let dv = new DataView(data);
+</xsl:text>
+ <xsl:text> let i = 0;
</xsl:text>
<xsl:text> try {
</xsl:text>
- <xsl:text> let idx = widget.offset ? index - widget.offset : index;
-</xsl:text>
- <xsl:text> let idxidx = widget.indexes.indexOf(idx);
-</xsl:text>
- <xsl:text> let d = widget.dispatch;
-</xsl:text>
- <xsl:text> if(typeof(d) == "function" && idxidx == 0){
-</xsl:text>
- <xsl:text> d.call(widget, value, oldval);
+ <xsl:text> for(let hash_int of hmi_hash) {
+</xsl:text>
+ <xsl:text> if(hash_int != dv.getUint8(i)){
+</xsl:text>
+ <xsl:text> throw new Error("Hash doesn't match");
+</xsl:text>
+ <xsl:text> };
+</xsl:text>
+ <xsl:text> i++;
+</xsl:text>
+ <xsl:text> };
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text> while(i < data.byteLength){
+</xsl:text>
+ <xsl:text> let index = dv.getUint32(i, true);
+</xsl:text>
+ <xsl:text> i += 4;
+</xsl:text>
+ <xsl:text> let iectype = hmitree_types[index];
+</xsl:text>
+ <xsl:text> if(iectype != undefined){
+</xsl:text>
+ <xsl:text> let dvgetter = dvgetters[iectype];
+</xsl:text>
+ <xsl:text> let [value, bytesize] = dvgetter(dv,i);
+</xsl:text>
+ <xsl:text> updates[index] = value;
+</xsl:text>
+ <xsl:text> i += bytesize;
+</xsl:text>
+ <xsl:text> } else {
+</xsl:text>
+ <xsl:text> throw new Error("Unknown index "+index);
+</xsl:text>
+ <xsl:text> }
+</xsl:text>
+ <xsl:text> };
+</xsl:text>
+ <xsl:text> // register for rendering on next frame, since there are updates
+</xsl:text>
+ <xsl:text> requestHMIAnimation();
+</xsl:text>
+ <xsl:text> } catch(err) {
+</xsl:text>
+ <xsl:text> // 1003 is for "Unsupported Data"
+</xsl:text>
+ <xsl:text> // ws.close(1003, err.message);
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text> // TODO : remove debug alert ?
+</xsl:text>
+ <xsl:text> alert("Error : "+err.message+"\nHMI will be reloaded.");
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text> // force reload ignoring cache
+</xsl:text>
+ <xsl:text> location.reload(true);
+</xsl:text>
+ <xsl:text> }
+</xsl:text>
+ <xsl:text>};
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text>function send_blob(data) {
+</xsl:text>
+ <xsl:text> if(data.length > 0) {
+</xsl:text>
+ <xsl:text> ws.send(new Blob([new Uint8Array(hmi_hash)].concat(data)));
+</xsl:text>
+ <xsl:text> };
+</xsl:text>
+ <xsl:text>};
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text>const typedarray_types = {
+</xsl:text>
+ <xsl:text> INT: (number) => new Int16Array([number]),
+</xsl:text>
+ <xsl:text> BOOL: (truth) => new Int16Array([truth]),
+</xsl:text>
+ <xsl:text> NODE: (truth) => new Int16Array([truth]),
+</xsl:text>
+ <xsl:text> STRING: (str) => {
+</xsl:text>
+ <xsl:text> // beremiz default string max size is 128
+</xsl:text>
+ <xsl:text> str = str.slice(0,128);
+</xsl:text>
+ <xsl:text> binary = new Uint8Array(str.length + 1);
+</xsl:text>
+ <xsl:text> binary[0] = str.length;
+</xsl:text>
+ <xsl:text> for(var i = 0; i < str.length; i++){
+</xsl:text>
+ <xsl:text> binary[i+1] = str.charCodeAt(i);
</xsl:text>
<xsl:text> }
</xsl:text>
- <xsl:text> else if(typeof(d) == "object" && d.length >= idxidx){
-</xsl:text>
- <xsl:text> d[idxidx].call(widget, value, oldval);
+ <xsl:text> return binary;
+</xsl:text>
+ <xsl:text> }
+</xsl:text>
+ <xsl:text> /* TODO */
+</xsl:text>
+ <xsl:text>};
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text>function send_reset() {
+</xsl:text>
+ <xsl:text> send_blob(new Uint8Array([1])); /* reset = 1 */
+</xsl:text>
+ <xsl:text>};
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text>// subscription state, as it should be in hmi server
+</xsl:text>
+ <xsl:text>// hmitree indexed array of integers
+</xsl:text>
+ <xsl:text>var subscriptions = hmitree_types.map(_ignored => 0);
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text>// subscription state as needed by widget now
+</xsl:text>
+ <xsl:text>// hmitree indexed array of Sets of widgets objects
+</xsl:text>
+ <xsl:text>var subscribers = hmitree_types.map(_ignored => new Set());
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text>// artificially subscribe the watchdog widget to "/heartbeat" hmi variable
+</xsl:text>
+ <xsl:text>// Since dispatch directly calls change_hmi_value,
+</xsl:text>
+ <xsl:text>// PLC will periodically send variable at given frequency
+</xsl:text>
+ <xsl:text>subscribers[heartbeat_index].add({
+</xsl:text>
+ <xsl:text> /* type: "Watchdog", */
+</xsl:text>
+ <xsl:text> frequency: 1,
+</xsl:text>
+ <xsl:text> indexes: [heartbeat_index],
+</xsl:text>
+ <xsl:text> new_hmi_value: function(index, value, oldval) {
+</xsl:text>
+ <xsl:text> apply_hmi_value(heartbeat_index, value+1);
+</xsl:text>
+ <xsl:text> }
+</xsl:text>
+ <xsl:text>});
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text>function update_subscriptions() {
+</xsl:text>
+ <xsl:text> let delta = [];
+</xsl:text>
+ <xsl:text> for(let index = 0; index < subscribers.length; index++){
+</xsl:text>
+ <xsl:text> let widgets = subscribers[index];
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text> // periods are in ms
+</xsl:text>
+ <xsl:text> let previous_period = subscriptions[index];
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text> // subscribing with a zero period is unsubscribing
+</xsl:text>
+ <xsl:text> let new_period = 0;
+</xsl:text>
+ <xsl:text> if(widgets.size > 0) {
+</xsl:text>
+ <xsl:text> let maxfreq = 0;
+</xsl:text>
+ <xsl:text> for(let widget of widgets){
+</xsl:text>
+ <xsl:text> let wf = widget.frequency;
+</xsl:text>
+ <xsl:text> if(wf != undefined && maxfreq < wf)
+</xsl:text>
+ <xsl:text> maxfreq = wf;
+</xsl:text>
+ <xsl:text> }
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text> if(maxfreq != 0)
+</xsl:text>
+ <xsl:text> new_period = 1000/maxfreq;
</xsl:text>
<xsl:text> }
</xsl:text>
- <xsl:text> /* else dispatch_0, ..., dispatch_n ? */
-</xsl:text>
- <xsl:text> /*else {
-</xsl:text>
- <xsl:text> throw new Error("Dunno how to dispatch to widget at index = " + index);
-</xsl:text>
- <xsl:text> }*/
-</xsl:text>
- <xsl:text> } catch(err) {
-</xsl:text>
- <xsl:text> console.log(err);
+ <xsl:text>
+</xsl:text>
+ <xsl:text> if(previous_period != new_period) {
+</xsl:text>
+ <xsl:text> subscriptions[index] = new_period;
+</xsl:text>
+ <xsl:text> delta.push(
+</xsl:text>
+ <xsl:text> new Uint8Array([2]), /* subscribe = 2 */
+</xsl:text>
+ <xsl:text> new Uint32Array([index]),
+</xsl:text>
+ <xsl:text> new Uint16Array([new_period]));
+</xsl:text>
+ <xsl:text> }
</xsl:text>
<xsl:text> }
</xsl:text>
+ <xsl:text> send_blob(delta);
+</xsl:text>
+ <xsl:text>};
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text>function send_hmi_value(index, value) {
+</xsl:text>
+ <xsl:text> let iectype = hmitree_types[index];
+</xsl:text>
+ <xsl:text> let tobinary = typedarray_types[iectype];
+</xsl:text>
+ <xsl:text> send_blob([
+</xsl:text>
+ <xsl:text> new Uint8Array([0]), /* setval = 0 */
+</xsl:text>
+ <xsl:text> new Uint32Array([index]),
+</xsl:text>
+ <xsl:text> tobinary(value)]);
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text> // DON'T DO THAT unless read_iterator in svghmi.c modifies wbuf as well, not only rbuf
+</xsl:text>
+ <xsl:text> // cache[index] = value;
+</xsl:text>
+ <xsl:text>};
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text>function apply_hmi_value(index, new_val) {
+</xsl:text>
+ <xsl:text> let old_val = cache[index]
+</xsl:text>
+ <xsl:text> if(new_val != undefined && old_val != new_val)
+</xsl:text>
+ <xsl:text> send_hmi_value(index, new_val);
+</xsl:text>
+ <xsl:text> return new_val;
+</xsl:text>
<xsl:text>}
</xsl:text>
<xsl:text>
</xsl:text>
- <xsl:text>function dispatch_value(index, value) {
-</xsl:text>
- <xsl:text> let widgets = subscribers[index];
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text> let oldval = cache[index];
-</xsl:text>
- <xsl:text> cache[index] = value;
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text> if(widgets.size > 0) {
-</xsl:text>
- <xsl:text> for(let widget of widgets){
-</xsl:text>
- <xsl:text> dispatch_value_to_widget(widget, index, value, oldval);
+ <xsl:text>quotes = {"'":null, '"':null};
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text>function change_hmi_value(index, opstr) {
+</xsl:text>
+ <xsl:text> let op = opstr[0];
+</xsl:text>
+ <xsl:text> let given_val;
+</xsl:text>
+ <xsl:text> if(opstr.length < 2)
+</xsl:text>
+ <xsl:text> return undefined; // TODO raise
+</xsl:text>
+ <xsl:text> if(opstr[1] in quotes){
+</xsl:text>
+ <xsl:text> if(opstr.length < 3)
+</xsl:text>
+ <xsl:text> return undefined; // TODO raise
+</xsl:text>
+ <xsl:text> if(opstr[opstr.length-1] == opstr[1]){
+</xsl:text>
+ <xsl:text> given_val = opstr.slice(2,opstr.length-1);
</xsl:text>
<xsl:text> }
</xsl:text>
+ <xsl:text> } else {
+</xsl:text>
+ <xsl:text> given_val = Number(opstr.slice(1));
+</xsl:text>
<xsl:text> }
</xsl:text>
+ <xsl:text> let old_val = cache[index];
+</xsl:text>
+ <xsl:text> let new_val;
+</xsl:text>
+ <xsl:text> switch(op){
+</xsl:text>
+ <xsl:text> case "=":
+</xsl:text>
+ <xsl:text> new_val = given_val;
+</xsl:text>
+ <xsl:text> break;
+</xsl:text>
+ <xsl:text> case "+":
+</xsl:text>
+ <xsl:text> new_val = old_val + given_val;
+</xsl:text>
+ <xsl:text> break;
+</xsl:text>
+ <xsl:text> case "-":
+</xsl:text>
+ <xsl:text> new_val = old_val - given_val;
+</xsl:text>
+ <xsl:text> break;
+</xsl:text>
+ <xsl:text> case "*":
+</xsl:text>
+ <xsl:text> new_val = old_val * given_val;
+</xsl:text>
+ <xsl:text> break;
+</xsl:text>
+ <xsl:text> case "/":
+</xsl:text>
+ <xsl:text> new_val = old_val / given_val;
+</xsl:text>
+ <xsl:text> break;
+</xsl:text>
+ <xsl:text> }
+</xsl:text>
+ <xsl:text> if(new_val != undefined && old_val != new_val)
+</xsl:text>
+ <xsl:text> send_hmi_value(index, new_val);
+</xsl:text>
+ <xsl:text> // TODO else raise
+</xsl:text>
+ <xsl:text> return new_val;
+</xsl:text>
+ <xsl:text>}
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text>var current_visible_page;
+</xsl:text>
+ <xsl:text>var current_subscribed_page;
+</xsl:text>
+ <xsl:text>var current_page_index;
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text>function prepare_svg() {
+</xsl:text>
+ <xsl:text> for(let eltid in detachable_elements){
+</xsl:text>
+ <xsl:text> let [element,parent] = detachable_elements[eltid];
+</xsl:text>
+ <xsl:text> parent.removeChild(element);
+</xsl:text>
+ <xsl:text> }
+</xsl:text>
<xsl:text>};
</xsl:text>
<xsl:text>
</xsl:text>
- <xsl:text>function init_widgets() {
-</xsl:text>
- <xsl:text> Object.keys(hmi_widgets).forEach(function(id) {
-</xsl:text>
- <xsl:text> let widget = hmi_widgets[id];
-</xsl:text>
- <xsl:text> let init = widget.init;
-</xsl:text>
- <xsl:text> if(typeof(init) == "function"){
-</xsl:text>
- <xsl:text> try {
-</xsl:text>
- <xsl:text> init.call(widget);
-</xsl:text>
- <xsl:text> } catch(err) {
-</xsl:text>
- <xsl:text> console.log(err);
-</xsl:text>
- <xsl:text> }
-</xsl:text>
- <xsl:text> }
-</xsl:text>
- <xsl:text> });
-</xsl:text>
- <xsl:text>};
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text>// Open WebSocket to relative "/ws" address
-</xsl:text>
- <xsl:text>var ws = new WebSocket(window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws'));
-</xsl:text>
- <xsl:text>ws.binaryType = 'arraybuffer';
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text>const dvgetters = {
-</xsl:text>
- <xsl:text> INT: (dv,offset) => [dv.getInt16(offset, true), 2],
-</xsl:text>
- <xsl:text> BOOL: (dv,offset) => [dv.getInt8(offset, true), 1],
-</xsl:text>
- <xsl:text> NODE: (dv,offset) => [dv.getInt8(offset, true), 1],
-</xsl:text>
- <xsl:text> STRING: (dv, offset) => {
-</xsl:text>
- <xsl:text> size = dv.getInt8(offset);
-</xsl:text>
- <xsl:text> return [
-</xsl:text>
- <xsl:text> String.fromCharCode.apply(null, new Uint8Array(
-</xsl:text>
- <xsl:text> dv.buffer, /* original buffer */
-</xsl:text>
- <xsl:text> offset + 1, /* string starts after size*/
-</xsl:text>
- <xsl:text> size /* size of string */
-</xsl:text>
- <xsl:text> )), size + 1]; /* total increment */
+ <xsl:text>function switch_page(page_name, page_index) {
+</xsl:text>
+ <xsl:text> if(current_subscribed_page != current_visible_page){
+</xsl:text>
+ <xsl:text> /* page switch already going */
+</xsl:text>
+ <xsl:text> /* TODO LOG ERROR */
+</xsl:text>
+ <xsl:text> return false;
</xsl:text>
<xsl:text> }
</xsl:text>
- <xsl:text>};
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text>// Apply updates recieved through ws.onmessage to subscribed widgets
-</xsl:text>
- <xsl:text>function apply_updates() {
-</xsl:text>
- <xsl:text> for(let index in updates){
-</xsl:text>
- <xsl:text> // serving as a key, index becomes a string
-</xsl:text>
- <xsl:text> // -> pass Number(index) instead
-</xsl:text>
- <xsl:text> dispatch_value(Number(index), updates[index]);
-</xsl:text>
- <xsl:text> delete updates[index];
+ <xsl:text>
+</xsl:text>
+ <xsl:text> if(page_name == undefined)
+</xsl:text>
+ <xsl:text> page_name = current_subscribed_page;
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text> let old_desc = page_desc[current_subscribed_page];
+</xsl:text>
+ <xsl:text> let new_desc = page_desc[page_name];
+</xsl:text>
+ <xsl:text>
+</xsl:text>
+ <xsl:text> if(new_desc == undefined){
+</xsl:text>
+ <xsl:text> /* TODO LOG ERROR */
+</xsl:text>
+ <xsl:text> return false;
</xsl:text>
<xsl:text> }
</xsl:text>
- <xsl:text>}
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text>// Called on requestAnimationFrame, modifies DOM
-</xsl:text>
- <xsl:text>var requestAnimationFrameID = null;
-</xsl:text>
- <xsl:text>function animate() {
-</xsl:text>
- <xsl:text> // Do the page swith if any one pending
-</xsl:text>
- <xsl:text> if(current_subscribed_page != current_visible_page){
-</xsl:text>
- <xsl:text> switch_visible_page(current_subscribed_page);
+ <xsl:text>
+</xsl:text>
+ <xsl:text> if(page_index == undefined){
+</xsl:text>
+ <xsl:text> page_index = new_desc.page_index;
</xsl:text>
<xsl:text> }
</xsl:text>
<xsl:text>
</xsl:text>
- <xsl:text> while(widget = need_cache_apply.pop()){
-</xsl:text>
- <xsl:text> widget.apply_cache();
+ <xsl:text> if(old_desc){
+</xsl:text>
+ <xsl:text> old_desc.widgets.map(([widget,relativeness])=>widget.unsub());
</xsl:text>
<xsl:text> }
</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text> if(jumps_need_update) update_jumps();
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text> apply_updates();
-</xsl:text>
- <xsl:text> requestAnimationFrameID = null;
-</xsl:text>
- <xsl:text>}
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text>function requestHMIAnimation() {
-</xsl:text>
- <xsl:text> if(requestAnimationFrameID == null){
-</xsl:text>
- <xsl:text> requestAnimationFrameID = window.requestAnimationFrame(animate);
-</xsl:text>
- <xsl:text> }
-</xsl:text>
- <xsl:text>}
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text>// Message reception handler
-</xsl:text>
- <xsl:text>// Hash is verified and HMI values updates resulting from binary parsing
-</xsl:text>
- <xsl:text>// are stored until browser can compute next frame, DOM is left untouched
-</xsl:text>
- <xsl:text>ws.onmessage = function (evt) {
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text> let data = evt.data;
-</xsl:text>
- <xsl:text> let dv = new DataView(data);
-</xsl:text>
- <xsl:text> let i = 0;
-</xsl:text>
- <xsl:text> try {
-</xsl:text>
- <xsl:text> for(let hash_int of hmi_hash) {
-</xsl:text>
- <xsl:text> if(hash_int != dv.getUint8(i)){
-</xsl:text>
- <xsl:text> throw new Error("Hash doesn't match");
-</xsl:text>
- <xsl:text> };
-</xsl:text>
- <xsl:text> i++;
-</xsl:text>
- <xsl:text> };
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text> while(i < data.byteLength){
-</xsl:text>
- <xsl:text> let index = dv.getUint32(i, true);
-</xsl:text>
- <xsl:text> i += 4;
-</xsl:text>
- <xsl:text> let iectype = hmitree_types[index];
-</xsl:text>
- <xsl:text> if(iectype != undefined){
-</xsl:text>
- <xsl:text> let dvgetter = dvgetters[iectype];
-</xsl:text>
- <xsl:text> let [value, bytesize] = dvgetter(dv,i);
-</xsl:text>
- <xsl:text> updates[index] = value;
-</xsl:text>
- <xsl:text> i += bytesize;
-</xsl:text>
- <xsl:text> } else {
-</xsl:text>
- <xsl:text> throw new Error("Unknown index "+index);
-</xsl:text>
- <xsl:text> }
-</xsl:text>
- <xsl:text> };
-</xsl:text>
- <xsl:text> // register for rendering on next frame, since there are updates
-</xsl:text>
- <xsl:text> requestHMIAnimation();
-</xsl:text>
- <xsl:text> } catch(err) {
-</xsl:text>
- <xsl:text> // 1003 is for "Unsupported Data"
-</xsl:text>
- <xsl:text> // ws.close(1003, err.message);
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text> // TODO : remove debug alert ?
-</xsl:text>
- <xsl:text> alert("Error : "+err.message+"\nHMI will be reloaded.");
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text> // force reload ignoring cache
-</xsl:text>
- <xsl:text> location.reload(true);
-</xsl:text>
- <xsl:text> }
-</xsl:text>
- <xsl:text>};
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text>function send_blob(data) {
-</xsl:text>
- <xsl:text> if(data.length > 0) {
-</xsl:text>
- <xsl:text> ws.send(new Blob([new Uint8Array(hmi_hash)].concat(data)));
-</xsl:text>
- <xsl:text> };
-</xsl:text>
- <xsl:text>};
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text>const typedarray_types = {
-</xsl:text>
- <xsl:text> INT: (number) => new Int16Array([number]),
-</xsl:text>
- <xsl:text> BOOL: (truth) => new Int16Array([truth]),
-</xsl:text>
- <xsl:text> NODE: (truth) => new Int16Array([truth]),
-</xsl:text>
- <xsl:text> STRING: (str) => {
-</xsl:text>
- <xsl:text> // beremiz default string max size is 128
-</xsl:text>
- <xsl:text> str = str.slice(0,128);
-</xsl:text>
- <xsl:text> binary = new Uint8Array(str.length + 1);
-</xsl:text>
- <xsl:text> binary[0] = str.length;
-</xsl:text>
- <xsl:text> for(var i = 0; i < str.length; i++){
-</xsl:text>
- <xsl:text> binary[i+1] = str.charCodeAt(i);
-</xsl:text>
- <xsl:text> }
-</xsl:text>
- <xsl:text> return binary;
-</xsl:text>
- <xsl:text> }
-</xsl:text>
- <xsl:text> /* TODO */
-</xsl:text>
- <xsl:text>};
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text>function send_reset() {
-</xsl:text>
- <xsl:text> send_blob(new Uint8Array([1])); /* reset = 1 */
-</xsl:text>
- <xsl:text>};
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text>// subscription state, as it should be in hmi server
-</xsl:text>
- <xsl:text>// hmitree indexed array of integers
-</xsl:text>
- <xsl:text>var subscriptions = hmitree_types.map(_ignored => 0);
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text>// subscription state as needed by widget now
-</xsl:text>
- <xsl:text>// hmitree indexed array of Sets of widgets objects
-</xsl:text>
- <xsl:text>var subscribers = hmitree_types.map(_ignored => new Set());
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text>// artificially subscribe the watchdog widget to "/heartbeat" hmi variable
-</xsl:text>
- <xsl:text>// Since dispatch directly calls change_hmi_value,
-</xsl:text>
- <xsl:text>// PLC will periodically send variable at given frequency
-</xsl:text>
- <xsl:text>subscribers[heartbeat_index].add({
-</xsl:text>
- <xsl:text> /* type: "Watchdog", */
-</xsl:text>
- <xsl:text> frequency: 1,
-</xsl:text>
- <xsl:text> indexes: [heartbeat_index],
-</xsl:text>
- <xsl:text> dispatch: function(value) {
-</xsl:text>
- <xsl:text> apply_hmi_value(heartbeat_index, value+1);
-</xsl:text>
- <xsl:text> }
-</xsl:text>
- <xsl:text>});
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text>function update_subscriptions() {
-</xsl:text>
- <xsl:text> let delta = [];
-</xsl:text>
- <xsl:text> for(let index = 0; index < subscribers.length; index++){
-</xsl:text>
- <xsl:text> let widgets = subscribers[index];
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text> // periods are in ms
-</xsl:text>
- <xsl:text> let previous_period = subscriptions[index];
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text> // subscribing with a zero period is unsubscribing
-</xsl:text>
- <xsl:text> let new_period = 0;
-</xsl:text>
- <xsl:text> if(widgets.size > 0) {
-</xsl:text>
- <xsl:text> let maxfreq = 0;
-</xsl:text>
- <xsl:text> for(let widget of widgets){
-</xsl:text>
- <xsl:text> let wf = widget.frequency;
-</xsl:text>
- <xsl:text> if(wf != undefined && maxfreq < wf)
-</xsl:text>
- <xsl:text> maxfreq = wf;
-</xsl:text>
- <xsl:text> }
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text> if(maxfreq != 0)
-</xsl:text>
- <xsl:text> new_period = 1000/maxfreq;
-</xsl:text>
- <xsl:text> }
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text> if(previous_period != new_period) {
-</xsl:text>
- <xsl:text> subscriptions[index] = new_period;
-</xsl:text>
- <xsl:text> delta.push(
-</xsl:text>
- <xsl:text> new Uint8Array([2]), /* subscribe = 2 */
-</xsl:text>
- <xsl:text> new Uint32Array([index]),
-</xsl:text>
- <xsl:text> new Uint16Array([new_period]));
-</xsl:text>
- <xsl:text> }
-</xsl:text>
- <xsl:text> }
-</xsl:text>
- <xsl:text> send_blob(delta);
-</xsl:text>
- <xsl:text>};
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text>function send_hmi_value(index, value) {
-</xsl:text>
- <xsl:text> let iectype = hmitree_types[index];
-</xsl:text>
- <xsl:text> let tobinary = typedarray_types[iectype];
-</xsl:text>
- <xsl:text> send_blob([
-</xsl:text>
- <xsl:text> new Uint8Array([0]), /* setval = 0 */
-</xsl:text>
- <xsl:text> new Uint32Array([index]),
-</xsl:text>
- <xsl:text> tobinary(value)]);
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text> // DON'T DO THAT unless read_iterator in svghmi.c modifies wbuf as well, not only rbuf
-</xsl:text>
- <xsl:text> // cache[index] = value;
-</xsl:text>
- <xsl:text>};
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text>function apply_hmi_value(index, new_val) {
-</xsl:text>
- <xsl:text> let old_val = cache[index]
-</xsl:text>
- <xsl:text> if(new_val != undefined && old_val != new_val)
-</xsl:text>
- <xsl:text> send_hmi_value(index, new_val);
-</xsl:text>
- <xsl:text> return new_val;
-</xsl:text>
- <xsl:text>}
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text>quotes = {"'":null, '"':null};
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text>function change_hmi_value(index, opstr) {
-</xsl:text>
- <xsl:text> let op = opstr[0];
-</xsl:text>
- <xsl:text> let given_val;
-</xsl:text>
- <xsl:text> if(opstr.length < 2)
-</xsl:text>
- <xsl:text> return undefined; // TODO raise
-</xsl:text>
- <xsl:text> if(opstr[1] in quotes){
-</xsl:text>
- <xsl:text> if(opstr.length < 3)
-</xsl:text>
- <xsl:text> return undefined; // TODO raise
-</xsl:text>
- <xsl:text> if(opstr[opstr.length-1] == opstr[1]){
-</xsl:text>
- <xsl:text> given_val = opstr.slice(2,opstr.length-1);
-</xsl:text>
- <xsl:text> }
-</xsl:text>
- <xsl:text> } else {
-</xsl:text>
- <xsl:text> given_val = Number(opstr.slice(1));
-</xsl:text>
- <xsl:text> }
-</xsl:text>
- <xsl:text> let old_val = cache[index];
-</xsl:text>
- <xsl:text> let new_val;
-</xsl:text>
- <xsl:text> switch(op){
-</xsl:text>
- <xsl:text> case "=":
-</xsl:text>
- <xsl:text> new_val = given_val;
-</xsl:text>
- <xsl:text> break;
-</xsl:text>
- <xsl:text> case "+":
-</xsl:text>
- <xsl:text> new_val = old_val + given_val;
-</xsl:text>
- <xsl:text> break;
-</xsl:text>
- <xsl:text> case "-":
-</xsl:text>
- <xsl:text> new_val = old_val - given_val;
-</xsl:text>
- <xsl:text> break;
-</xsl:text>
- <xsl:text> case "*":
-</xsl:text>
- <xsl:text> new_val = old_val * given_val;
-</xsl:text>
- <xsl:text> break;
-</xsl:text>
- <xsl:text> case "/":
-</xsl:text>
- <xsl:text> new_val = old_val / given_val;
-</xsl:text>
- <xsl:text> break;
-</xsl:text>
- <xsl:text> }
-</xsl:text>
- <xsl:text> if(new_val != undefined && old_val != new_val)
-</xsl:text>
- <xsl:text> send_hmi_value(index, new_val);
-</xsl:text>
- <xsl:text> // TODO else raise
-</xsl:text>
- <xsl:text> return new_val;
-</xsl:text>
- <xsl:text>}
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text>var current_visible_page;
-</xsl:text>
- <xsl:text>var current_subscribed_page;
-</xsl:text>
- <xsl:text>var current_page_index;
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text>function prepare_svg() {
-</xsl:text>
- <xsl:text> for(let eltid in detachable_elements){
-</xsl:text>
- <xsl:text> let [element,parent] = detachable_elements[eltid];
-</xsl:text>
- <xsl:text> parent.removeChild(element);
-</xsl:text>
- <xsl:text> }
-</xsl:text>
- <xsl:text>};
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text>function switch_page(page_name, page_index) {
-</xsl:text>
- <xsl:text> if(current_subscribed_page != current_visible_page){
-</xsl:text>
- <xsl:text> /* page switch already going */
-</xsl:text>
- <xsl:text> /* TODO LOG ERROR */
-</xsl:text>
- <xsl:text> return false;
-</xsl:text>
- <xsl:text> }
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text> if(page_name == undefined)
-</xsl:text>
- <xsl:text> page_name = current_subscribed_page;
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text> let old_desc = page_desc[current_subscribed_page];
-</xsl:text>
- <xsl:text> let new_desc = page_desc[page_name];
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text> if(new_desc == undefined){
-</xsl:text>
- <xsl:text> /* TODO LOG ERROR */
-</xsl:text>
- <xsl:text> return false;
-</xsl:text>
- <xsl:text> }
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text> if(page_index == undefined){
-</xsl:text>
- <xsl:text> page_index = new_desc.page_index;
-</xsl:text>
- <xsl:text> }
-</xsl:text>
- <xsl:text>
-</xsl:text>
- <xsl:text> if(old_desc){
-</xsl:text>
- <xsl:text> old_desc.absolute_widgets.map(w=>w.unsub());
-</xsl:text>
- <xsl:text> old_desc.relative_widgets.map(w=>w.unsub());
-</xsl:text>
- <xsl:text> }
-</xsl:text>
- <xsl:text> new_desc.absolute_widgets.map(w=>w.sub());
-</xsl:text>
<xsl:text> var new_offset = page_index == undefined ? 0 : page_index - new_desc.page_index;
</xsl:text>
- <xsl:text> new_desc.relative_widgets.map(w=>w.sub(new_offset));
+ <xsl:text> new_desc.widgets.map(([widget,relativeness])=>widget.sub(new_offset,relativeness));
</xsl:text>
<xsl:text>
</xsl:text>