# HG changeset patch # User Edouard Tisserant # Date 1584450097 -3600 # Node ID 3bb49f93d48c6ef03d69b66efac610a75ce54a69 # Parent 9da4ac0c9add7e3887736a1fedf847df43dda354 SVGHMI: added widget_common.ysl2 diff -r 9da4ac0c9add -r 3bb49f93d48c svghmi/gen_index_xhtml.xslt --- a/svghmi/gen_index_xhtml.xslt Tue Mar 17 13:43:19 2020 +0100 +++ b/svghmi/gen_index_xhtml.xslt Tue Mar 17 14:01:37 2020 +0100 @@ -527,157 +527,22 @@ - - - Made with SVGHMI. https://beremiz.org - - - - - debug_hmitree: - - - - - - - - - debug_geometry: - - - - - - - - - debug_detachables: - - - - - - - - - debug_unlink: - - - - - - - - - - - - - - - //(function(){ - - - - id = idstr => document.getElementById(idstr); - - - - var hmi_hash = [ - - ]; - - var hmi_widgets = { - - - - - " - - ": { - - type: " - - ", - - args: [ - - - " - - " - - , - - - - - ], - - indexes: [ - - - - - - Widget - - id=" - - " : No match for path " - - " in HMI tree - - - - - - - , - - - - - - - ], - - element: id(" - - "), - - - - - } - - , - - - - - } - - - - var heartbeat_index = - - ; - - - - var hmitree_types = [ - - - /* - - - - */ " - + + + + " + + ": { + + type: " + + ", + + args: [ + + + " + " , @@ -685,826 +550,48 @@ - ] - - - - var detachable_elements = { - - - " - - ":[id(" - - "), id(" - - ")] - - , - - - + ], + + indexes: [ + + + + + + Widget + + id=" + + " : No match for path " + + " in HMI tree + + + + + + + , + + + + + - } - - - - var page_desc = { - - - } - - - - var default_page = " - - "; - - var svg_root = id(" - - "); - - // svghmi.js - - - - var cache = hmitree_types.map(_ignored => undefined); - - var updates = {}; - - - - function dispatch_value_to_widget(widget, index, value, oldval) { - - try { - - let idx = widget.offset ? index - widget.offset : index; - - let idxidx = widget.indexes.indexOf(idx); - - let d = widget.dispatch; - - console.log(index, idx, idxidx, value); - - if(typeof(d) == "function" && idxidx == 0){ - - d.call(widget, value, oldval); - - } - - else if(typeof(d) == "object" && d.length >= idxidx){ - - d[idxidx].call(widget, value, oldval); - - } - - /* else dispatch_0, ..., dispatch_n ? */ - - /*else { - - throw new Error("Dunno how to dispatch to widget at index = " + index); - - }*/ - - } catch(err) { - - console.log(err); - - } - - } - - - - function dispatch_value(index, value) { - - let widgets = subscribers[index]; - - - - let oldval = cache[index]; - - cache[index] = value; - - - - if(widgets.size > 0) { - - for(let widget of widgets){ - - dispatch_value_to_widget(widget, index, value, oldval); - - } - - } - - }; - - - - function init_widgets() { - - Object.keys(hmi_widgets).forEach(function(id) { - - let widget = hmi_widgets[id]; - - let init = widget.init; - - if(typeof(init) == "function"){ - - try { - - init.call(widget); - - } catch(err) { - - console.log(err); - - } - - } - - }); - - }; - - - - // Open WebSocket to relative "/ws" address - - var ws = new WebSocket(window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws')); - - ws.binaryType = 'arraybuffer'; - - - - const dvgetters = { - - INT: (dv,offset) => [dv.getInt16(offset, true), 2], - - BOOL: (dv,offset) => [dv.getInt8(offset, true), 1], - - STRING: (dv, offset) => { - - size = dv.getInt8(offset); - - return [ - - String.fromCharCode.apply(null, new Uint8Array( - - dv.buffer, /* original buffer */ - - offset + 1, /* string starts after size*/ - - size /* size of string */ - - )), size + 1]; /* total increment */ - - } - - }; - - - - // Apply updates recieved through ws.onmessage to subscribed widgets - - function apply_updates() { - - for(let index in updates){ - - // serving as a key, index becomes a string - - // -> pass Number(index) instead - - dispatch_value(Number(index), updates[index]); - - delete updates[index]; - - } - - } - - - - // Called on requestAnimationFrame, modifies DOM - - var requestAnimationFrameID = null; - - function animate() { - - // Do the page swith if any one pending - - if(current_subscribed_page != current_visible_page){ - - switch_visible_page(current_subscribed_page); - - } - - apply_updates(); - - requestAnimationFrameID = null; - - } - - - - function requestHMIAnimation() { - - if(requestAnimationFrameID == null){ - - requestAnimationFrameID = window.requestAnimationFrame(animate); - - } - - } - - - - // 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) { - - - - let data = evt.data; - - let dv = new DataView(data); - - let i = 0; - - try { - - for(let hash_int of hmi_hash) { - - if(hash_int != dv.getUint8(i)){ - - throw new Error("Hash doesn't match"); - - }; - - i++; - - }; - - - - while(i < data.byteLength){ - - let index = dv.getUint32(i, true); - - i += 4; - - let iectype = hmitree_types[index]; - - if(iectype != undefined){ - - let dvgetter = dvgetters[iectype]; - - let [value, bytesize] = dvgetter(dv,i); - - updates[index] = value; - - i += bytesize; - - } else { - - throw new Error("Unknown index "+index); - - } - - }; - - // register for rendering on next frame, since there are updates - - requestHMIAnimation(); - - } catch(err) { - - // 1003 is for "Unsupported Data" - - // ws.close(1003, err.message); - - - - // TODO : remove debug alert ? - - alert("Error : "+err.message+"\nHMI will be reloaded."); - - - - // force reload ignoring cache - - location.reload(true); - - } - - }; - - - - - - function send_blob(data) { - - if(data.length > 0) { - - ws.send(new Blob([new Uint8Array(hmi_hash)].concat(data))); - - }; - - }; - - - - const typedarray_types = { - - INT: (number) => new Int16Array([number]), - - BOOL: (truth) => new Int16Array([truth]), - - STRING: (str) => { - - // beremiz default string max size is 128 - - str = str.slice(0,128); - - binary = new Uint8Array(str.length + 1); - - binary[0] = str.length; - - for(var i = 0; i < str.length; i++){ - - binary[i+1] = str.charCodeAt(i); - - } - - return binary; - - } - - /* TODO */ - - }; - - - - function send_reset() { - - send_blob(new Uint8Array([1])); /* reset = 1 */ - - }; - - - - // subscription state, as it should be in hmi server - - // hmitree indexed array of integers - - var subscriptions = hmitree_types.map(_ignored => 0); - - - - // subscription state as needed by widget now - - // hmitree indexed array of Sets of widgets objects - - var subscribers = hmitree_types.map(_ignored => new Set()); - - - - // artificially subscribe the watchdog widget to "/heartbeat" hmi variable - - // Since dispatch directly calls change_hmi_value, - - // PLC will periodically send variable at given frequency - - subscribers[heartbeat_index].add({ - - /* type: "Watchdog", */ - - frequency: 1, - - indexes: [heartbeat_index], - - dispatch: function(value) { - - // console.log("Heartbeat" + value); - - change_hmi_value(heartbeat_index, "+1"); - - } - - }); - - - - function update_subscriptions() { - - let delta = []; - - for(let index = 0; index < subscribers.length; index++){ - - let widgets = subscribers[index]; - - - - // periods are in ms - - let previous_period = subscriptions[index]; - - - - // subscribing with a zero period is unsubscribing - - let new_period = 0; - - if(widgets.size > 0) { - - let maxfreq = 0; - - for(let widget of widgets) - - if(maxfreq < widget.frequency) - - maxfreq = widget.frequency; - - - - if(maxfreq != 0) - - new_period = 1000/maxfreq; - - } - - - - if(previous_period != new_period) { - - subscriptions[index] = new_period; - - delta.push( - - new Uint8Array([2]), /* subscribe = 2 */ - - new Uint32Array([index]), - - new Uint16Array([new_period])); - - } - - } - - send_blob(delta); - - }; - - - - function send_hmi_value(index, value) { - - let iectype = hmitree_types[index]; - - let tobinary = typedarray_types[iectype]; - - send_blob([ - - new Uint8Array([0]), /* setval = 0 */ - - new Uint32Array([index]), - - tobinary(value)]); - - - - cache[index] = value; - - }; - - - - function change_hmi_value(index, opstr) { - - let op = opstr[0]; - - let given_val = opstr.slice(1); - - let old_val = cache[index] - - let new_val; - - switch(op){ - - case "=": - - eval("new_val"+opstr); - - break; - - case "+": - - case "-": - - case "*": - - case "/": - - if(old_val != undefined) - - new_val = eval("old_val"+opstr); - - break; - - } - - if(new_val != undefined && old_val != new_val) - - send_hmi_value(index, new_val); - - return new_val; - - } - - - - var current_visible_page; - - var current_subscribed_page; - - - - function prepare_svg() { - - for(let eltid in detachable_elements){ - - let [element,parent] = detachable_elements[eltid]; - - parent.removeChild(element); - - } - - }; - - - - function switch_page(page_name, page_index) { - - if(current_subscribed_page != current_visible_page){ - - /* page switch already going */ - - /* TODO LOG ERROR */ - - return; - - } else if(page_name == current_visible_page){ - - /* already in that page */ - - /* TODO LOG ERROR */ - - return; - - } - - switch_subscribed_page(page_name, page_index); - - }; - - - - function* chain(a,b){ - - yield* a; - - yield* b; - - }; - - - - function switch_subscribed_page(page_name, page_index) { - - let old_desc = page_desc[current_subscribed_page]; - - let new_desc = page_desc[page_name]; - - - - if(new_desc == undefined){ - - /* TODO LOG ERROR */ - - return; - - } - - - - if(page_index == undefined){ - - page_index = new_desc.page_index; - - } - - - - if(old_desc){ - - for(let widget of old_desc.absolute_widgets){ - - /* remove subsribers */ - - for(let index of widget.indexes){ - - subscribers[index].delete(widget); - - } - - } - - for(let widget of old_desc.relative_widgets){ - - /* remove subsribers */ - - for(let index of widget.indexes){ - - let idx = widget.offset ? index + widget.offset : index; - - subscribers[idx].delete(widget); - - } - - /* lose the offset */ - - delete widget.offset; - - } - - } - - for(let widget of new_desc.absolute_widgets){ - - /* add widget's subsribers */ - - for(let index of widget.indexes){ - - subscribers[index].add(widget); - - } - - } - - var new_offset = page_index == undefined ? 0 : page_index - new_desc.page_index; - - for(let widget of new_desc.relative_widgets){ - - /* set the offset because relative */ - - widget.offset = new_offset; - - /* add widget's subsribers */ - - for(let index of widget.indexes){ - - subscribers[index + new_offset].add(widget); - - } - - } - - - - update_subscriptions(); - - - - current_subscribed_page = page_name; - - - - requestHMIAnimation(); - - } - - - - function switch_visible_page(page_name) { - - - - let old_desc = page_desc[current_visible_page]; - - let new_desc = page_desc[page_name]; - - - - if(old_desc){ - - for(let eltid in old_desc.required_detachables){ - - if(!(eltid in new_desc.required_detachables)){ - - let [element, parent] = old_desc.required_detachables[eltid]; - - parent.removeChild(element); - - } - - } - - for(let eltid in new_desc.required_detachables){ - - if(!(eltid in old_desc.required_detachables)){ - - let [element, parent] = new_desc.required_detachables[eltid]; - - parent.appendChild(element); - - } - - } - - }else{ - - for(let eltid in new_desc.required_detachables){ - - let [element, parent] = new_desc.required_detachables[eltid]; - - parent.appendChild(element); - - } - - } - - - - for(let widget of chain(new_desc.absolute_widgets,new_desc.relative_widgets)){ - - for(let index of widget.indexes){ - - /* dispatch current cache in newly opened page widgets */ - - let cached_val = cache[index]; - - if(cached_val != undefined) - - dispatch_value_to_widget(widget, index, cached_val, cached_val); - - } - - } - - - - svg_root.setAttribute('viewBox',new_desc.bbox.join(" ")); - - current_visible_page = page_name; - - }; - - - - - - // Once connection established - - ws.onopen = function (evt) { - - init_widgets(); - - send_reset(); - - // show main page - - prepare_svg(); - - switch_page(default_page); - - }; - - - - ws.onclose = function (evt) { - - // 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+"."); - - - - }; - - //})(); + ], + + element: id(" + + "), + + + + + } + + , + + @@ -1537,6 +624,920 @@ + + + Made with SVGHMI. https://beremiz.org + + + + + debug_hmitree: + + + + + + + + + debug_geometry: + + + + + + + + + debug_detachables: + + + + + + + + + debug_unlink: + + + + + + + + + + + + + + + //(function(){ + + + + id = idstr => document.getElementById(idstr); + + + + var hmi_hash = [ + + ]; + + var hmi_widgets = { + + + } + + + + var heartbeat_index = + + ; + + + + var hmitree_types = [ + + + /* + + + + */ " + + " + + , + + + + + ] + + + + var detachable_elements = { + + + " + + ":[id(" + + "), id(" + + ")] + + , + + + + + } + + + + var page_desc = { + + + } + + + + var default_page = " + + "; + + var svg_root = id(" + + "); + + // svghmi.js + + + + var cache = hmitree_types.map(_ignored => undefined); + + var updates = {}; + + + + function dispatch_value_to_widget(widget, index, value, oldval) { + + try { + + let idx = widget.offset ? index - widget.offset : index; + + let idxidx = widget.indexes.indexOf(idx); + + let d = widget.dispatch; + + console.log(index, idx, idxidx, value); + + if(typeof(d) == "function" && idxidx == 0){ + + d.call(widget, value, oldval); + + } + + else if(typeof(d) == "object" && d.length >= idxidx){ + + d[idxidx].call(widget, value, oldval); + + } + + /* else dispatch_0, ..., dispatch_n ? */ + + /*else { + + throw new Error("Dunno how to dispatch to widget at index = " + index); + + }*/ + + } catch(err) { + + console.log(err); + + } + + } + + + + function dispatch_value(index, value) { + + let widgets = subscribers[index]; + + + + let oldval = cache[index]; + + cache[index] = value; + + + + if(widgets.size > 0) { + + for(let widget of widgets){ + + dispatch_value_to_widget(widget, index, value, oldval); + + } + + } + + }; + + + + function init_widgets() { + + Object.keys(hmi_widgets).forEach(function(id) { + + let widget = hmi_widgets[id]; + + let init = widget.init; + + if(typeof(init) == "function"){ + + try { + + init.call(widget); + + } catch(err) { + + console.log(err); + + } + + } + + }); + + }; + + + + // Open WebSocket to relative "/ws" address + + var ws = new WebSocket(window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws')); + + ws.binaryType = 'arraybuffer'; + + + + const dvgetters = { + + INT: (dv,offset) => [dv.getInt16(offset, true), 2], + + BOOL: (dv,offset) => [dv.getInt8(offset, true), 1], + + STRING: (dv, offset) => { + + size = dv.getInt8(offset); + + return [ + + String.fromCharCode.apply(null, new Uint8Array( + + dv.buffer, /* original buffer */ + + offset + 1, /* string starts after size*/ + + size /* size of string */ + + )), size + 1]; /* total increment */ + + } + + }; + + + + // Apply updates recieved through ws.onmessage to subscribed widgets + + function apply_updates() { + + for(let index in updates){ + + // serving as a key, index becomes a string + + // -> pass Number(index) instead + + dispatch_value(Number(index), updates[index]); + + delete updates[index]; + + } + + } + + + + // Called on requestAnimationFrame, modifies DOM + + var requestAnimationFrameID = null; + + function animate() { + + // Do the page swith if any one pending + + if(current_subscribed_page != current_visible_page){ + + switch_visible_page(current_subscribed_page); + + } + + apply_updates(); + + requestAnimationFrameID = null; + + } + + + + function requestHMIAnimation() { + + if(requestAnimationFrameID == null){ + + requestAnimationFrameID = window.requestAnimationFrame(animate); + + } + + } + + + + // 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) { + + + + let data = evt.data; + + let dv = new DataView(data); + + let i = 0; + + try { + + for(let hash_int of hmi_hash) { + + if(hash_int != dv.getUint8(i)){ + + throw new Error("Hash doesn't match"); + + }; + + i++; + + }; + + + + while(i < data.byteLength){ + + let index = dv.getUint32(i, true); + + i += 4; + + let iectype = hmitree_types[index]; + + if(iectype != undefined){ + + let dvgetter = dvgetters[iectype]; + + let [value, bytesize] = dvgetter(dv,i); + + updates[index] = value; + + i += bytesize; + + } else { + + throw new Error("Unknown index "+index); + + } + + }; + + // register for rendering on next frame, since there are updates + + requestHMIAnimation(); + + } catch(err) { + + // 1003 is for "Unsupported Data" + + // ws.close(1003, err.message); + + + + // TODO : remove debug alert ? + + alert("Error : "+err.message+"\nHMI will be reloaded."); + + + + // force reload ignoring cache + + location.reload(true); + + } + + }; + + + + + + function send_blob(data) { + + if(data.length > 0) { + + ws.send(new Blob([new Uint8Array(hmi_hash)].concat(data))); + + }; + + }; + + + + const typedarray_types = { + + INT: (number) => new Int16Array([number]), + + BOOL: (truth) => new Int16Array([truth]), + + STRING: (str) => { + + // beremiz default string max size is 128 + + str = str.slice(0,128); + + binary = new Uint8Array(str.length + 1); + + binary[0] = str.length; + + for(var i = 0; i < str.length; i++){ + + binary[i+1] = str.charCodeAt(i); + + } + + return binary; + + } + + /* TODO */ + + }; + + + + function send_reset() { + + send_blob(new Uint8Array([1])); /* reset = 1 */ + + }; + + + + // subscription state, as it should be in hmi server + + // hmitree indexed array of integers + + var subscriptions = hmitree_types.map(_ignored => 0); + + + + // subscription state as needed by widget now + + // hmitree indexed array of Sets of widgets objects + + var subscribers = hmitree_types.map(_ignored => new Set()); + + + + // artificially subscribe the watchdog widget to "/heartbeat" hmi variable + + // Since dispatch directly calls change_hmi_value, + + // PLC will periodically send variable at given frequency + + subscribers[heartbeat_index].add({ + + /* type: "Watchdog", */ + + frequency: 1, + + indexes: [heartbeat_index], + + dispatch: function(value) { + + // console.log("Heartbeat" + value); + + change_hmi_value(heartbeat_index, "+1"); + + } + + }); + + + + function update_subscriptions() { + + let delta = []; + + for(let index = 0; index < subscribers.length; index++){ + + let widgets = subscribers[index]; + + + + // periods are in ms + + let previous_period = subscriptions[index]; + + + + // subscribing with a zero period is unsubscribing + + let new_period = 0; + + if(widgets.size > 0) { + + let maxfreq = 0; + + for(let widget of widgets) + + if(maxfreq < widget.frequency) + + maxfreq = widget.frequency; + + + + if(maxfreq != 0) + + new_period = 1000/maxfreq; + + } + + + + if(previous_period != new_period) { + + subscriptions[index] = new_period; + + delta.push( + + new Uint8Array([2]), /* subscribe = 2 */ + + new Uint32Array([index]), + + new Uint16Array([new_period])); + + } + + } + + send_blob(delta); + + }; + + + + function send_hmi_value(index, value) { + + let iectype = hmitree_types[index]; + + let tobinary = typedarray_types[iectype]; + + send_blob([ + + new Uint8Array([0]), /* setval = 0 */ + + new Uint32Array([index]), + + tobinary(value)]); + + + + cache[index] = value; + + }; + + + + function change_hmi_value(index, opstr) { + + let op = opstr[0]; + + let given_val = opstr.slice(1); + + let old_val = cache[index] + + let new_val; + + switch(op){ + + case "=": + + eval("new_val"+opstr); + + break; + + case "+": + + case "-": + + case "*": + + case "/": + + if(old_val != undefined) + + new_val = eval("old_val"+opstr); + + break; + + } + + if(new_val != undefined && old_val != new_val) + + send_hmi_value(index, new_val); + + return new_val; + + } + + + + var current_visible_page; + + var current_subscribed_page; + + + + function prepare_svg() { + + for(let eltid in detachable_elements){ + + let [element,parent] = detachable_elements[eltid]; + + parent.removeChild(element); + + } + + }; + + + + function switch_page(page_name, page_index) { + + if(current_subscribed_page != current_visible_page){ + + /* page switch already going */ + + /* TODO LOG ERROR */ + + return; + + } else if(page_name == current_visible_page){ + + /* already in that page */ + + /* TODO LOG ERROR */ + + return; + + } + + switch_subscribed_page(page_name, page_index); + + }; + + + + function* chain(a,b){ + + yield* a; + + yield* b; + + }; + + + + function switch_subscribed_page(page_name, page_index) { + + let old_desc = page_desc[current_subscribed_page]; + + let new_desc = page_desc[page_name]; + + + + if(new_desc == undefined){ + + /* TODO LOG ERROR */ + + return; + + } + + + + if(page_index == undefined){ + + page_index = new_desc.page_index; + + } + + + + if(old_desc){ + + for(let widget of old_desc.absolute_widgets){ + + /* remove subsribers */ + + for(let index of widget.indexes){ + + subscribers[index].delete(widget); + + } + + } + + for(let widget of old_desc.relative_widgets){ + + /* remove subsribers */ + + for(let index of widget.indexes){ + + let idx = widget.offset ? index + widget.offset : index; + + subscribers[idx].delete(widget); + + } + + /* lose the offset */ + + delete widget.offset; + + } + + } + + for(let widget of new_desc.absolute_widgets){ + + /* add widget's subsribers */ + + for(let index of widget.indexes){ + + subscribers[index].add(widget); + + } + + } + + var new_offset = page_index == undefined ? 0 : page_index - new_desc.page_index; + + for(let widget of new_desc.relative_widgets){ + + /* set the offset because relative */ + + widget.offset = new_offset; + + /* add widget's subsribers */ + + for(let index of widget.indexes){ + + subscribers[index + new_offset].add(widget); + + } + + } + + + + update_subscriptions(); + + + + current_subscribed_page = page_name; + + + + requestHMIAnimation(); + + } + + + + function switch_visible_page(page_name) { + + + + let old_desc = page_desc[current_visible_page]; + + let new_desc = page_desc[page_name]; + + + + if(old_desc){ + + for(let eltid in old_desc.required_detachables){ + + if(!(eltid in new_desc.required_detachables)){ + + let [element, parent] = old_desc.required_detachables[eltid]; + + parent.removeChild(element); + + } + + } + + for(let eltid in new_desc.required_detachables){ + + if(!(eltid in old_desc.required_detachables)){ + + let [element, parent] = new_desc.required_detachables[eltid]; + + parent.appendChild(element); + + } + + } + + }else{ + + for(let eltid in new_desc.required_detachables){ + + let [element, parent] = new_desc.required_detachables[eltid]; + + parent.appendChild(element); + + } + + } + + + + for(let widget of chain(new_desc.absolute_widgets,new_desc.relative_widgets)){ + + for(let index of widget.indexes){ + + /* dispatch current cache in newly opened page widgets */ + + let cached_val = cache[index]; + + if(cached_val != undefined) + + dispatch_value_to_widget(widget, index, cached_val, cached_val); + + } + + } + + + + svg_root.setAttribute('viewBox',new_desc.bbox.join(" ")); + + current_visible_page = page_name; + + }; + + + + + + // Once connection established + + ws.onopen = function (evt) { + + init_widgets(); + + send_reset(); + + // show main page + + prepare_svg(); + + switch_page(default_page); + + }; + + + + ws.onclose = function (evt) { + + // 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+"."); + + + + }; + + //})(); + + frequency: 5, diff -r 9da4ac0c9add -r 3bb49f93d48c svghmi/gen_index_xhtml.ysl2 --- a/svghmi/gen_index_xhtml.ysl2 Tue Mar 17 13:43:19 2020 +0100 +++ b/svghmi/gen_index_xhtml.ysl2 Tue Mar 17 14:01:37 2020 +0100 @@ -60,6 +60,8 @@ include inline_svg.ysl2 + include widget_common.ysl2 + template "/" { comment > Made with SVGHMI. https://beremiz.org @@ -120,31 +122,7 @@ */ | var hmi_widgets = { - foreach "$hmi_elements" { - const "widget", "func:parselabel(@inkscape:label)/widget"; - const "eltid","@id"; - | "«@id»": { - | type: "«$widget/@type»", - | args: [ - foreach "$widget/arg" - | "«@value»"`if "position()!=last()" > ,` - | ], - | indexes: [ - foreach "$widget/path" { - choose { - when "not(@index)" { - warning > Widget «$widget/@type» id="«$eltid»" : No match for path "«@value»" in HMI tree - } - otherwise { - | «@index»`if "position()!=last()" > ,` - } - } - } - | ], - | element: id("«@id»"), - apply "$widget", mode="widget_defs" with "hmi_element","."; - | }`if "position()!=last()" > ,` - } + apply "$hmi_elements", mode="hmi_elements"; | } | | var heartbeat_index = «$indexed_hmitree/*[@hmipath = '/HEARTBEAT']/@index»; @@ -207,31 +185,6 @@ // } - - function "defs_by_labels" { - param "labels","''"; - param "mandatory","'yes'"; - param "hmi_element"; - const "widget_type","@type"; - foreach "str:split($labels)" { - const "name","."; - const "elt_id","$result_svg_ns//*[@id = $hmi_element/@id]//*[@inkscape:label=$name][1]/@id"; - choose { - when "not($elt_id)" { - if "$mandatory='yes'" { - // TODO FIXME error > «$widget_type» widget must have a «$name» element - warning > «$widget_type» widget must have a «$name» element - } - // otherwise produce nothing - } - otherwise { - | «$name»_elt: id("«$elt_id»"), - } - } - } - } - - template "widget[@type='Display']", mode="widget_defs" { param "hmi_element"; | frequency: 5, diff -r 9da4ac0c9add -r 3bb49f93d48c svghmi/widget_common.ysl2 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/svghmi/widget_common.ysl2 Tue Mar 17 14:01:37 2020 +0100 @@ -0,0 +1,61 @@ +in xsl decl labels(*ptr, name="defs_by_labels") alias call-template { + with "hmi_element", "$hmi_element"; + with "labels"{text *ptr}; +}; + +in xsl decl optional_labels(*ptr, name="defs_by_labels") alias call-template { + with "hmi_element", "$hmi_element"; + with "labels"{text *ptr}; + with "mandatory","'no'"; +}; + +template "svg:*", mode="hmi_elements" { + const "widget", "func:parselabel(@inkscape:label)/widget"; + const "eltid","@id"; + | "«@id»": { + | type: "«$widget/@type»", + | args: [ + foreach "$widget/arg" + | "«@value»"`if "position()!=last()" > ,` + | ], + | indexes: [ + foreach "$widget/path" { + choose { + when "not(@index)" { + warning > Widget «$widget/@type» id="«$eltid»" : No match for path "«@value»" in HMI tree + } + otherwise { + | «@index»`if "position()!=last()" > ,` + } + } + } + | ], + | element: id("«@id»"), + apply "$widget", mode="widget_defs" with "hmi_element","."; + | }`if "position()!=last()" > ,` +} + + +function "defs_by_labels" { + param "labels","''"; + param "mandatory","'yes'"; + param "hmi_element"; + const "widget_type","@type"; + foreach "str:split($labels)" { + const "name","."; + const "elt_id","$result_svg_ns//*[@id = $hmi_element/@id]//*[@inkscape:label=$name][1]/@id"; + choose { + when "not($elt_id)" { + if "$mandatory='yes'" { + // TODO FIXME error > «$widget_type» widget must have a «$name» element + warning > «$widget_type» widget must have a «$name» element + } + // otherwise produce nothing + } + otherwise { + | «$name»_elt: id("«$elt_id»"), + } + } + } +} +