beremiz

SVGHMI : had to move the problem of wkaing up python thread from plc thread to platform specific code.
Since Xenomai's cobalt thread are definitely incompatible with normal posix python interpreter binary's thread, we must synchronize them with arcane rt_pipes (the only ones that really work cross domain) as already done in debug and python async eval blocks.
include yslt_noindent.yml2
// overrides yslt's output function to set CDATA
decl output(method, cdata-section-elements="xhtml:script");
in xsl decl labels(*ptr, name="defs_by_labels") alias call-template {
with "hmi_element", "$hmi_element";
with "labels"{text *ptr};
};
istylesheet
/* From Inkscape */
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:xhtml="http://www.w3.org/1999/xhtml"
/* Our namespace to invoke python code */
xmlns:ns="beremiz"
extension-element-prefixes="ns func"
exclude-result-prefixes="ns str regexp exsl func" {
/* This retrieves geometry obtained through "inkscape -S"
* already parsed by python and presented as a list of
* <bbox x="0" y="0" w="42" h="42">
*/
const "geometry", "ns:GetSVGGeometry()";
const "hmitree", "ns:GetHMITree()";
const "svg_root_id", "/svg:svg/@id";
const "hmi_elements", "//svg:*[starts-with(@inkscape:label, 'HMI:')]";
const "hmi_geometry", "$geometry[@Id = $hmi_elements/@id]";
const "hmi_pages", "$hmi_elements[func:parselabel(@inkscape:label)/widget/@type = 'Page']";
const "default_page" choose {
when "count($hmi_pages) > 1" {
const "Home_page",
"$hmi_pages[func:parselabel(@inkscape:label)/widget/arg[1]/@value = 'Home']";
choose {
when "$Home_page" > Home
otherwise {
error "No Home page defined!";
}
}
}
when "count($hmi_pages) = 0" {
error "No page defined!";
}
otherwise > «func:parselabel($hmi_pages/@inkscape:label)/widget/arg[1]/@value»
}
const "_categories" {
noindex > HMI_ROOT
noindex > HMI_NODE
noindex > HMI_PLC_STATUS
noindex > HMI_CURRENT_PAGE
}
const "categories", "exsl:node-set($_categories)";
const "_indexed_hmitree" apply "$hmitree", mode="index";
const "indexed_hmitree", "exsl:node-set($_indexed_hmitree)";
template "*", mode="index" {
param "index", "0";
param "parentpath", "''";
const "content" {
const "path"
choose {
when "local-name() = 'HMI_ROOT'" > «$parentpath»
otherwise > «$parentpath»/«@name»
}
choose {
when "not(local-name() = $categories/noindex)" {
xsl:copy {
attrib "index" > «$index»
attrib "hmipath" > «$path»
foreach "@*" xsl:copy;
}
/* no node expected below value nodes */
}
otherwise {
apply "*[1]", mode="index"{
with "index", "$index";
with "parentpath" > «$path»
}
}
}
}
copy "$content";
apply "following-sibling::*[1]", mode="index" {
with "index", "$index + count(exsl:node-set($content)/*)";
with "parentpath" > «$parentpath»
}
}
/* Identity template :
* - copy every attributes
* - copy every sub-elements
*/
template "@* | node()", mode="inline_svg" {
/* use real xsl:copy instead copy-of alias from yslt.yml2 */
xsl:copy apply "@* | node()", mode="inline_svg";
}
/* replaces inkscape's height and width hints. forces fit */
template "svg:svg/@width", mode="inline_svg";
template "svg:svg/@height", mode="inline_svg";
template "svg:svg", mode="inline_svg" xsl:copy {
attrib "preserveAspectRatio" > none
attrib "height" > 100vh
attrib "width" > 100vw
apply "@* | node()", mode="inline_svg";
}
/*const "mark" > =HMI=\n*/
/* copy root node and add geometry as comment for a test */
template "/" {
comment > Made with SVGHMI. https://beremiz.org
/* DEBUG DATA */
comment {
apply "$hmi_geometry", mode="testgeo";
}
comment {
apply "$hmitree", mode="testtree";
}
comment {
apply "$indexed_hmitree", mode="testtree";
}
/**/
html xmlns="http://www.w3.org/1999/xhtml" {
head;
body style="margin:0;overflow:hidden;" {
apply "svg:svg", mode="inline_svg";
script{
call "scripts";
}
}
}
}
/*
Parses:
"HMI:WidgetType:param1:param2@path1@path2"
Into:
widget type="WidgetType" {
arg value="param1";
arg value="param2";
path value="path1";
path value="path2";
}
*/
func:function name="func:parselabel" {
param "label";
const "description", "substring-after($label,'HMI:')";
const "_args", "substring-before($description,'@')";
const "args" choose {
when "$_args" value "$_args";
otherwise value "$description";
}
const "_type", "substring-before($args,':')";
const "type" choose {
when "$_type" value "$_type";
otherwise value "$args";
}
const "ast" if "$type" widget {
attrib "type" > «$type»
foreach "str:split(substring-after($args, ':'), ':')" {
arg {
attrib "value" > «.»
}
}
const "paths", "substring-after($description,'@')";
foreach "str:split($paths, '@')" {
path {
attrib "value" > «.»
}
}
}
func:result select="exsl:node-set($ast)"
}
function "scripts"
{
| //(function(){
|
| var hmi_hash = [«$hmitree/@hash»];
/* TODO re-enable
||
function evaluate_js_from_descriptions() {
var Page;
var Input;
var Display;
var res = [];
||
const "midmark" > \n«$mark»
apply """//*[contains(child::svg:desc, $midmark) or \
starts-with(child::svg:desc, $mark)]""",2
mode="code_from_descs";
||
return res;
}
||
*/
| var hmi_widgets = {
foreach "$hmi_elements" {
const "widget", "func:parselabel(@inkscape:label)/widget";
| «@id»: {
| type: "«$widget/@type»",
| args: [
foreach "$widget/arg"
| "«@value»"`if "position()!=last()" > ,`
| ],
| indexes: [
foreach "$widget/path" {
const "hmipath","@value";
const "hmitree_match","$indexed_hmitree/*[@hmipath = $hmipath]";
if "count($hmitree_match) = 0"
error > No match for path "«$hmipath»" in HMI tree
| «$hmitree_match/@index»`if "position()!=last()" > ,`
}
| ],
| element: document.getElementById("«@id»"),
apply "$widget", mode="widget_defs" with "hmi_element",".";
| }`if "position()!=last()" > ,`
}
| }
|
| var hmitree_types = [
foreach "$indexed_hmitree/*" {
| /* «@index» «@hmipath» */ "«substring(local-name(), 5)»"`if "position()!=last()" > ,`
}
| ]
|
| var page_desc = {
foreach "$hmi_pages" {
const "desc", "func:parselabel(@inkscape:label)/widget";
const "page", ".";
const "p", "$hmi_geometry[@Id = $page/@id]";
const "page_ids","""$hmi_geometry[@Id != $page/@id and
@x >= $p/@x and @y >= $p/@y and
@x+@w <= $p/@x+$p/@w and @y+@h <= $p/@y+$p/@h]/@Id""";
const "page_elements", "$hmi_elements[@id = $page_ids]";
| "«$desc/arg[1]/@value»": {
| id: "«@id»",
| bbox: [«$p/@x», «$p/@y», «$p/@w», «$p/@h»],
| widgets: [
foreach "$page_ids" {
| hmi_widgets.«.»`if "position()!=last()" > ,`
}
| ]
| }`if "position()!=last()" > ,`
}
| }
|
| var default_page = "«$default_page»";
| var svg_root = document.getElementById("«$svg_root_id»");
include text svghmi.js
| //})();
}
// template "*", mode="code_from_descs" {
// ||
// {
// var path, role, name, priv;
// var id = "«@id»";
// ||
// /* if label is used, use it as default name */
// if "@inkscape:label"
// |> name = "«@inkscape:label»";
// | /* -------------- */
// // this breaks indent, but fixing indent could break string literals
// value "substring-after(svg:desc, $mark)";
// // nobody reads generated code anyhow...
// ||
// /* -------------- */
// res.push({
// path:path,
// role:role,
// name:name,
// priv:priv
// })
// }
// ||
// }
/**/
template "bbox", mode="testgeo"{
| ID: «@Id» x: «@x» y: «@y» w: «@w» h: «@h»
}
template "*", mode="testtree"{
param "indent", "''";
> «$indent» «local-name()»
foreach "@*" > «local-name()»=«.»
> \n
apply "*", mode="testtree" {
with "indent" value "concat($indent,'>')"
};
}
/**/
function "defs_by_labels" {
param "labels","''";
param "mandatory","'yes'";
param "hmi_element";
foreach "str:split($labels)" {
const "name",".";
const "elt_id","$hmi_element//*[@inkscape:label=$name][1]/@id";
if "$mandatory='yes' and not($elt_id)" error > Meter widget must have a «$name» element
| «$name»_elt: document.getElementById("«$elt_id»"),
}
}
template "widget[@type='Display']", mode="widget_defs" {
param "hmi_element";
| frequency: 5,
| dispatch: function(value) {
choose {
when "$hmi_element[self::svg:text]"{
// TODO : care about <tspan> ?
| this.element.textContent = String(value);
}
otherwise {
error > Display widget as a group not implemented
}
}
| },
}
template "widget[@type='Meter']", mode="widget_defs" {
param "hmi_element";
| frequency: 10,
labels("value min max needle range");
| dispatch: function(value) {
| this.value_elt.textContent = String(value);
| let [min,max,totallength] = this.range;
| let length = Math.max(0,Math.min(totallength,(Number(value)-min)*totallength/(max-min)));
| let tip = this.range_elt.getPointAtLength(length);
// TODO : deal with transformations between needle and range
| this.needle_elt.setAttribute('d', "M "+this.origin.x+","+this.origin.y+" "+tip.x+","+tip.y);
| },
| origin: undefined,
| range: undefined,
| init: function() {
| this.range = [Number(this.min_elt.textContent), Number(this.max_elt.textContent), this.range_elt.getTotalLength()]
| this.origin = this.needle_elt.getPointAtLength(0);
| },
}
template "widget[@type='Input']", mode="widget_defs" {
param "hmi_element";
| frequency: 5,
labels("value");
| dispatch: function(value) {
| this.value_elt.textContent = String(value);
| },
const "edit_elt_id","$hmi_element/*[@inkscape:label='edit'][1]/@id";
| init: function() {
if "$edit_elt_id" {
| document.getElementById("«$edit_elt_id»").addEventListener(
| "click",
| evt => alert('XXX TODO : Edit value'));
}
foreach "$hmi_element/*[regexp:test(@inkscape:label,'^[=+\-][0-9]+')]" {
| document.getElementById("«@id»").addEventListener(
| "click",
| evt => {let new_val = change_hmi_value(this.indexes[0], "«@inkscape:label»");
| this.value_elt.textContent = String(new_val);});
/* could gray out value until refreshed */
}
| },
}
template "widget[@type='Button']", mode="widget_defs" {
}
template "widget[@type='Toggle']", mode="widget_defs" {
| frequency: 5,
}
template "widget[@type='Change']", mode="widget_defs" {
| frequency: 5,
}
template "widget[@type='Jump']", mode="widget_defs" {
| init: function() {
| this.element.addEventListener(
| "click",
| evt => switch_page(this.args[0]));
| },
}
}