# HG changeset patch # User Edouard Tisserant # Date 1569319111 -7200 # Node ID 35eeb1ed105f9e9b6267b548b7b58099fc502a9b # Parent 5ee6967f721d92cadebc39edfe6a135cbd3c21d1# Parent 39d78c530cbbc9724154d9cea7ab7b5394e76f00 Merge default in SVGHMI branch diff -r 39d78c530cbb -r 35eeb1ed105f PLCGenerator.py --- a/PLCGenerator.py Tue Sep 24 11:55:59 2019 +0200 +++ b/PLCGenerator.py Tue Sep 24 11:58:31 2019 +0200 @@ -935,7 +935,7 @@ if invar.getformalParameter() == "EN": if len(invar.getconnectionPointIn().getconnections()) > 0: if blk.getinstanceName() is None: - var_name = "%s%d_ENO" % (blk.gettypeName(), blk.getlocalId()) + var_name = "_TMP_%s%d_ENO" % (blk.gettypeName(), blk.getlocalId()) else: var_name = "%s.ENO" % blk.getinstanceName() return var_name @@ -1160,7 +1160,7 @@ if variable.getformalParameter() == "": variable_name = "%s%d" % (type, block.getlocalId()) else: - variable_name = "%s%d_%s" % (type, block.getlocalId(), parameter) + variable_name = "_TMP_%s%d_%s" % (type, block.getlocalId(), parameter) if self.Interface[-1][0] != "VAR" or self.Interface[-1][1] is not None or self.Interface[-1][2]: self.Interface.append(("VAR", None, False, [])) if variable.connectionPointOut in self.ConnectionTypes: @@ -1253,7 +1253,7 @@ if output_parameter == "": output_name = "%s%d" % (type, block.getlocalId()) else: - output_name = "%s%d_%s" % (type, block.getlocalId(), output_parameter) + output_name = "_TMP_%s%d_%s" % (type, block.getlocalId(), output_parameter) output_value = [(output_name, output_info)] return self.ExtractModifier(output_variable, output_value, output_info) if block_infos["type"] == "functionBlock": diff -r 39d78c530cbb -r 35eeb1ed105f ProjectController.py --- a/ProjectController.py Tue Sep 24 11:55:59 2019 +0200 +++ b/ProjectController.py Tue Sep 24 11:58:31 2019 +0200 @@ -1053,7 +1053,8 @@ "FB": "extern %(type)s %(C_path)s;" }[v["vartype"]] % v for v in self._VariablesList if v["C_path"].find('.') < 0]), - "variable_decl_array": ",\n".join(variable_decl_array) + "variable_decl_array": ",\n".join(variable_decl_array), + "var_access_code": targets.GetCode("var_access.c") } return debug_code @@ -1163,6 +1164,19 @@ def _Generate_runtime(self): buildpath = self._getBuildPath() + # CTN code gen is expected AFTER Libraries code gen, + # at least SVGHMI relies on it. + + # Generate C code and compilation params from liraries + try: + LibCFilesAndCFLAGS, LibLDFLAGS, LibExtraFiles = self.GetLibrariesCCode( + buildpath) + except Exception: + self.logger.write_error( + _("Runtime library extensions C code generation failed !\n")) + self.logger.write_error(traceback.format_exc()) + return False + # Generate C code and compilation params from confnode hierarchy try: CTNLocationCFilesAndCFLAGS, CTNLDFLAGS, CTNExtraFiles = self._Generate_C( @@ -1174,16 +1188,6 @@ self.logger.write_error(traceback.format_exc()) return False - # Generate C code and compilation params from liraries - try: - LibCFilesAndCFLAGS, LibLDFLAGS, LibExtraFiles = self.GetLibrariesCCode( - buildpath) - except Exception: - self.logger.write_error( - _("Runtime library extensions C code generation failed !\n")) - self.logger.write_error(traceback.format_exc()) - return False - self.LocationCFilesAndCFLAGS = LibCFilesAndCFLAGS + \ CTNLocationCFilesAndCFLAGS self.LDFLAGS = CTNLDFLAGS + LibLDFLAGS diff -r 39d78c530cbb -r 35eeb1ed105f XSLTransform.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/XSLTransform.py Tue Sep 24 11:58:31 2019 +0200 @@ -0,0 +1,25 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# This file is part of Beremiz. +# See COPYING file for copyrights details. + +from __future__ import absolute_import +from lxml import etree + +class XSLTransform(object): + """ a class to handle XSLT queries on project and libs """ + def __init__(self, xsltpath, xsltext): + + # parse and compile. "beremiz" arbitrary namespace for extensions + self.xslt = etree.XSLT( + etree.parse( + xsltpath, + etree.XMLParser()), + extensions={("beremiz", name): call for name, call in xsltext}) + + def transform(self, root, **kwargs): + res = self.xslt(root, **{k: etree.XSLT.strparam(v) for k, v in kwargs.iteritems()}) + # print(self.xslt.error_log) + return res + + diff -r 39d78c530cbb -r 35eeb1ed105f docutil/docsvg.py --- a/docutil/docsvg.py Tue Sep 24 11:55:59 2019 +0200 +++ b/docutil/docsvg.py Tue Sep 24 11:58:31 2019 +0200 @@ -31,15 +31,19 @@ def get_inkscape_path(): """ Return the Inkscape path """ - from six.moves import winreg - try: - svgexepath = winreg.QueryValue(winreg.HKEY_LOCAL_MACHINE, - 'Software\\Classes\\svgfile\\shell\\Inkscape\\command') - except OSError: - svgexepath = winreg.QueryValue(winreg.HKEY_LOCAL_MACHINE, - 'Software\\Classes\\inkscape.svg\\shell\\open\\command') - svgexepath = svgexepath.replace('"%1"', '') - return svgexepath.replace('"', '') + if wx.Platform == '__WXMSW__': + from six.moves import winreg + try: + svgexepath = winreg.QueryValue(winreg.HKEY_LOCAL_MACHINE, + 'Software\\Classes\\svgfile\\shell\\Inkscape\\command') + except OSError: + svgexepath = winreg.QueryValue(winreg.HKEY_LOCAL_MACHINE, + 'Software\\Classes\\inkscape.svg\\shell\\open\\command') + svgexepath = svgexepath.replace('"%1"', '').strip() + return svgexepath.replace('"', '') + else: + # TODO: search path + return os.path.join("/usr/bin", "inkscape") def open_win_svg(svgexepath, svgfile): @@ -65,7 +69,7 @@ wx.MessageBox("Inkscape is not found or installed !") return None else: - svgexepath = os.path.join("/usr/bin", "inkscape") + svgexepath=get_inkscape_path() if os.path.isfile(svgexepath): open_lin_svg(svgexepath, svgfile) else: diff -r 39d78c530cbb -r 35eeb1ed105f features.py --- a/features.py Tue Sep 24 11:55:59 2019 +0200 +++ b/features.py Tue Sep 24 11:58:31 2019 +0200 @@ -12,7 +12,8 @@ ('Native', 'NativeLib.NativeLibrary', True), ('Python', 'py_ext.PythonLibrary', True), ('Etherlab', 'etherlab.EthercatMaster.EtherlabLibrary', False), - ('SVGUI', 'svgui.SVGUILibrary', False)] + ('SVGUI', 'svgui.SVGUILibrary', False), + ('SVGHMI', 'svghmi.SVGHMILibrary', False)] catalog = [ ('canfestival', _('CANopen support'), _('Map located variables over CANopen'), 'canfestival.canfestival.RootClass'), @@ -22,6 +23,7 @@ ('c_ext', _('C extension'), _('Add C code accessing located variables synchronously'), 'c_ext.CFile'), ('py_ext', _('Python file'), _('Add Python code executed asynchronously'), 'py_ext.PythonFile'), ('wxglade_hmi', _('WxGlade GUI'), _('Add a simple WxGlade based GUI.'), 'wxglade_hmi.WxGladeHMI'), - ('svgui', _('SVGUI'), _('Experimental web based HMI'), 'svgui.SVGUI')] + ('svgui', _('SVGUI'), _('Experimental web based HMI'), 'svgui.SVGUI'), + ('svghmi', _('SVGHMI'), _('SVG based HMI'), 'svghmi.SVGHMI')] file_editors = [] diff -r 39d78c530cbb -r 35eeb1ed105f plcopen/Makefile --- a/plcopen/Makefile Tue Sep 24 11:55:59 2019 +0200 +++ b/plcopen/Makefile Tue Sep 24 11:58:31 2019 +0200 @@ -1,13 +1,23 @@ #! gmake -yml := ../../yml2 +# Makefile to generate XSLT stylesheets from ysl2 files in the same directory + +# This uses YML2. +# hg clone https://pep.foundation/dev/repos/yml2/ + +# It should be just fine if yml2 is cloned just asside beremiz +# otherwise, point yml2path to yml2 source directory +# make yml2path=path/to/yml/dir + +yml2path ?= $(abspath ../../yml2) + ysl2files := $(wildcard *.ysl2) xsltfiles := $(patsubst %.ysl2, %.xslt, $(ysl2files)) all:$(xsltfiles) -%.xslt: %.ysl2 yslt_noindent.yml2 - $(yml)/yml2c -I $(yml) $< -o $@.tmp +%.xslt: %.ysl2 ../yslt_noindent.yml2 + $(yml2path)/yml2c -I $(yml2path):../ $< -o $@.tmp xmlstarlet fo $@.tmp > $@ rm $@.tmp diff -r 39d78c530cbb -r 35eeb1ed105f plcopen/XSLTModelQuery.py --- a/plcopen/XSLTModelQuery.py Tue Sep 24 11:55:59 2019 +0200 +++ b/plcopen/XSLTModelQuery.py Tue Sep 24 11:58:31 2019 +0200 @@ -8,13 +8,14 @@ from lxml import etree import util.paths as paths from plcopen.structures import StdBlckLibs +from XSLTransform import XSLTransform ScriptDirectory = paths.AbsDir(__file__) - -class XSLTModelQuery(object): +class XSLTModelQuery(XSLTransform): """ a class to handle XSLT queries on project and libs """ def __init__(self, controller, xsltpath, ext=None): + # arbitrary set debug to false, updated later self.debug = False @@ -31,19 +32,12 @@ if ext is not None: xsltext.extend(ext) - # parse and compile. "beremiz" arbitrary namespace for extensions - self.xslt = etree.XSLT( - etree.parse( - os.path.join(ScriptDirectory, xsltpath), - etree.XMLParser()), - extensions={("beremiz", name): call for name, call in xsltext}) - + XSLTransform.__init__(self, + os.path.join(ScriptDirectory, xsltpath), + xsltext) def _process_xslt(self, root, debug, **kwargs): self.debug = debug - res = self.xslt(root, **{k: etree.XSLT.strparam(v) for k, v in kwargs.iteritems()}) - # print(self.xslt.error_log) - return res - + return self.transform(root, **kwargs) # ------------------------------------------------------------------------------- # Helpers functions for translating list of arguments diff -r 39d78c530cbb -r 35eeb1ed105f plcopen/yslt_noindent.yml2 --- a/plcopen/yslt_noindent.yml2 Tue Sep 24 11:55:59 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,32 +0,0 @@ -include yslt.yml2 - -in xsl { - decl istylesheet ( - *output="xml", - version="1.0", - xmlns:xsl="http://www.w3.org/1999/XSL/Transform", - xmlns:exsl='http://exslt.org/common', - extension-element-prefixes='exsl' - ) alias stylesheet { - output *output; - content; - }; - - decl template(match) { - content; - }; - - decl function(name) alias template { - content; - }; - - decl call(name) alias call-template { - content; - }; - - decl apply(select) alias apply-templates { - content; - }; -} - - diff -r 39d78c530cbb -r 35eeb1ed105f runtime/PLCObject.py --- a/runtime/PLCObject.py Tue Sep 24 11:55:59 2019 +0200 +++ b/runtime/PLCObject.py Tue Sep 24 11:58:31 2019 +0200 @@ -319,12 +319,15 @@ return False - def PythonRuntimeCall(self, methodname, use_evaluator=True): + def PythonRuntimeCall(self, methodname, use_evaluator=True, reverse_order=False): """ Calls init, start, stop or cleanup method provided by runtime python files, loaded when new PLC uploaded """ - for method in self.python_runtime_vars.get("_runtime_%s" % methodname, []): + methods = self.python_runtime_vars.get("_runtime_%s" % methodname, []) + if reverse_order: + methods = reversed(methods) + for method in methods: if use_evaluator: _res, exp = self.evaluator(method) else: @@ -395,7 +398,7 @@ if self.python_runtime_vars is not None: self.PythonThreadCommand("Finish") self.PythonThread.join() - self.PythonRuntimeCall("cleanup", use_evaluator=False) + self.PythonRuntimeCall("cleanup", use_evaluator=False, reverse_order=True) self.python_runtime_vars = None @@ -438,7 +441,7 @@ if cmd == "Activate": self.PythonRuntimeCall("start") self.PythonThreadLoop() - self.PythonRuntimeCall("stop") + self.PythonRuntimeCall("stop", reverse_order=True) else: # "Finish" break diff -r 39d78c530cbb -r 35eeb1ed105f svghmi/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/svghmi/Makefile Tue Sep 24 11:58:31 2019 +0200 @@ -0,0 +1,25 @@ +#! gmake + +# Makefile to generate XSLT stylesheets from ysl2 files in the same directory + +# This uses YML2. +# hg clone https://pep.foundation/dev/repos/yml2/ + +# It should be just fine if yml2 is cloned just asside beremiz +# otherwise, point yml2path to yml2 source directory +# make yml2path=path/to/yml/dir + +yml2path ?= $(abspath ../../yml2) + +ysl2files := $(wildcard *.ysl2) +xsltfiles := $(patsubst %.ysl2, %.xslt, $(ysl2files)) + +all:$(xsltfiles) + +%.xslt: %.ysl2 ../yslt_noindent.yml2 + $(yml2path)/yml2c -I $(yml2path):../ $< -o $@.tmp + xmlstarlet fo $@.tmp > $@ + rm $@.tmp + +clean: + rm -f $(xsltfiles) diff -r 39d78c530cbb -r 35eeb1ed105f svghmi/README --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/svghmi/README Tue Sep 24 11:58:31 2019 +0200 @@ -0,0 +1,1 @@ +SVG HMI diff -r 39d78c530cbb -r 35eeb1ed105f svghmi/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/svghmi/__init__.py Tue Sep 24 11:58:31 2019 +0200 @@ -0,0 +1,10 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# This file is part of Beremiz +# Copyright (C) 2019: Edouard TISSERANT +# +# See COPYING file for copyrights details. + +from __future__ import absolute_import +from svghmi.svghmi import * diff -r 39d78c530cbb -r 35eeb1ed105f svghmi/default.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/svghmi/default.svg Tue Sep 24 11:58:31 2019 +0200 @@ -0,0 +1,92 @@ + + + + + + + + image/svg+xml + + + + + + + + + This is description for page 0 + +all lines in the form "name: value" +are used as js object definition initializer + +role: "page" +name: "Home" + +after triple opening braces is global JavaScript code + +{{{ +/* JS style Comment */ +alert("Hello World"); +}}} + +after triple closing braces is back to description + + + path: "count" +format: "%4.4d"8888 + diff -r 39d78c530cbb -r 35eeb1ed105f svghmi/gen_index_xhtml.xslt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/svghmi/gen_index_xhtml.xslt Tue Sep 24 11:58:31 2019 +0200 @@ -0,0 +1,145 @@ + + + + + + + + + + + + =HMI= + + + + + + + + + + + + + + + + + + + + + { + + var path, role, name, priv; + + var id = " + + "; + + + name = " + + "; + + + /* -------------- */ + + + + + /* -------------- */ + + res.push({ + + path:path, + + role:role, + + name:name, + + priv:priv + + }) + + } + + + + ID: + + x: + + y: + + w: + + h: + + + + + + + + + + + + + + + + + + + + + + + + diff -r 39d78c530cbb -r 35eeb1ed105f svghmi/gen_index_xhtml.ysl2 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/svghmi/gen_index_xhtml.ysl2 Tue Sep 24 11:58:31 2019 +0200 @@ -0,0 +1,122 @@ +include yslt_noindent.yml2 + +// overrides yslt's output function to set CDATA +decl output(method, cdata-section-elements="script"); +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="http://www.w3.org/1999/xhtml" + + /* Our namespace to invoke python code */ + xmlns:ns="beremiz" + extension-element-prefixes="ns" + exclude-result-prefixes="ns" { + + /* This retrieves geometry obtained through "inkscape -S" + * already parsed by python and presented as a list of + * + */ + variable "geometry", "ns:GetSVGGeometry()"; + variable "hmitree", "ns:GetHMITree()"; + + /* Identity template : + * - copy every attributes + * - copy every sub-elements + */ + template "@* | node()" { + /* use real xsl:copy instead copy-of alias from yslt.yml2 */ + xsl:copy apply "@* | node()"; + } + + variable "mark" > =HMI=\n + + /* copy root node and add geometry as comment for a test */ + template "/" + html xmlns="http://www.w3.org/1999/xhtml" { + head; + body style="margin:0;" { + xsl:copy { + comment { + apply "$geometry", mode="testgeo"; + } + comment { + apply "$hmitree", mode="testtree"; + } + apply "@* | node()"; + } + script{ + || + function evaluate_js_from_descriptions() { + var Page; + var Input; + var Display; + var res = []; + || + variable "midmark" > \n«$mark» + apply """//*[contains(child::svg:desc, $midmark) or \ + starts-with(child::svg:desc, $mark)]""",2 + mode="code_from_descs"; + || + return res; + } + || + + /*TODO add : + - pages content + + with ref to elt ? + - widgets parameters + */ + + 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()» «@name» «@type» «@path» + apply "*", mode="testtree" { + with "indent" value "concat($indent,'>')" + }; + } +} diff -r 39d78c530cbb -r 35eeb1ed105f svghmi/pous.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/svghmi/pous.xml Tue Sep 24 11:58:31 2019 +0200 @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r 39d78c530cbb -r 35eeb1ed105f svghmi/svghmi.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/svghmi/svghmi.c Tue Sep 24 11:58:31 2019 +0200 @@ -0,0 +1,213 @@ +#include +#include +#include "iec_types_all.h" +#include "POUS.h" +#include "config.h" +#include "beremiz.h" + +#define DEFAULT_REFRESH_PERIOD_MS 100 +#define HMI_BUFFER_SIZE %(buffer_size)d +#define HMI_ITEM_COUNT %(item_count)d + +/* PLC reads from that buffer */ +static char rbuf[HMI_BUFFER_SIZE]; + +/* PLC writes to that buffer */ +static char wbuf[HMI_BUFFER_SIZE]; + +%(extern_variables_declarations)s + +#define ticktime_ns %(PLC_ticktime)d +uint16_t ticktime_ms = (ticktime_ns>1000000)? + ticktime_ns/1000000: + 1; + +typedef enum { + buf_free = 0, + buf_set, + buf_tosend +} buf_state_t; + +int global_write_dirty = 0; + +typedef struct { + void *ptr; + __IEC_types_enum type; + uint32_t buf_index; + + /* publish/write/send */ + long wlock; + /* zero means not subscribed */ + uint16_t refresh_period_ms; + uint16_t age_ms; + + buf_state_t wstate; + + /* retrieve/read/recv */ + long rlock; + buf_state_t rstate; + +} hmi_tree_item_t; + +static hmi_tree_item_t hmi_tree_item[] = { +%(variable_decl_array)s +}; + +static char sendbuf[HMI_BUFFER_SIZE]; + +typedef void(*hmi_tree_iterator)(hmi_tree_item_t*); +void traverse_hmi_tree(hmi_tree_iterator fp) +{ + unsigned int i; + for(i = 0; i < sizeof(hmi_tree_item)/sizeof(hmi_tree_item_t); i++){ + hmi_tree_item_t *dsc = &hmi_tree_item[i]; + if(dsc->type != UNKNOWN_ENUM) + (*fp)(dsc); + } +} + +#define __Unpack_desc_type hmi_tree_item_t + +%(var_access_code)s + +void write_iterator(hmi_tree_item_t *dsc) +{ + void *dest_p = &wbuf[dsc->buf_index]; + void *real_value_p = NULL; + char flags = 0; + + void *visible_value_p = UnpackVar(dsc, &real_value_p, &flags); + + /* Try take lock */ + long was_locked = AtomicCompareExchange(&dsc->wlock, 0, 1); + + if(was_locked) { + /* was locked. give up*/ + return; + } + + if(dsc->wstate == buf_set){ + /* if being subscribed */ + if(dsc->refresh_period_ms){ + if(dsc->age_ms + ticktime_ms < dsc->refresh_period_ms){ + dsc->age_ms += ticktime_ms; + }else{ + dsc->wstate = buf_tosend; + } + } + } + + /* if new value differs from previous one */ + if(memcmp(dest_p, visible_value_p, __get_type_enum_size(dsc->type)) != 0){ + /* copy and flag as set */ + memcpy(dest_p, visible_value_p, __get_type_enum_size(dsc->type)); + if(dsc->wstate == buf_free) { + dsc->wstate = buf_set; + dsc->age_ms = 0; + } + global_write_dirty = 1; + } + + /* unlock - use AtomicComparExchange to have memory barrier */ + AtomicCompareExchange(&dsc->wlock, 1, 0); +} + +struct timespec sending_now; +struct timespec next_sending; +void send_iterator(hmi_tree_item_t *dsc) +{ + while(AtomicCompareExchange(&dsc->wlock, 0, 1)) sched_yield(); + + // check for variable being modified + if(dsc->wstate == buf_tosend){ + // send + + // TODO pack data in buffer + + dsc->wstate = buf_free; + } + + AtomicCompareExchange(&dsc->wlock, 1, 0); +} + +void read_iterator(hmi_tree_item_t *dsc) +{ + void *src_p = &rbuf[dsc->buf_index]; + void *real_value_p = NULL; + char flags = 0; + + void *visible_value_p = UnpackVar(dsc, &real_value_p, &flags); + + + memcpy(visible_value_p, src_p, __get_type_enum_size(dsc->type)); +} + +static pthread_cond_t svghmi_send_WakeCond = PTHREAD_COND_INITIALIZER; +static pthread_mutex_t svghmi_send_WakeCondLock = PTHREAD_MUTEX_INITIALIZER; + +static int continue_collect; + +int __init_svghmi() +{ + bzero(rbuf,sizeof(rbuf)); + bzero(wbuf,sizeof(wbuf)); + continue_collect = 1; + + return 0; +} + +void __cleanup_svghmi() +{ + pthread_mutex_lock(&svghmi_send_WakeCondLock); + continue_collect = 0; + pthread_cond_signal(&svghmi_send_WakeCond); + pthread_mutex_unlock(&svghmi_send_WakeCondLock); +} + +void __retrieve_svghmi() +{ + traverse_hmi_tree(read_iterator); +} + +void __publish_svghmi() +{ + global_write_dirty = 0; + traverse_hmi_tree(write_iterator); + if(global_write_dirty) { + pthread_cond_signal(&svghmi_send_WakeCond); + } +} + +/* PYTHON CALLS */ +int svghmi_send_collect(uint32_t *size, char **ptr){ + + int do_collect; + pthread_mutex_lock(&svghmi_send_WakeCondLock); + do_collect = continue_collect; + if(do_collect){ + pthread_cond_wait(&svghmi_send_WakeCond, &svghmi_send_WakeCondLock); + do_collect = continue_collect; + } + pthread_mutex_unlock(&svghmi_send_WakeCondLock); + + + if(do_collect) { + traverse_hmi_tree(send_iterator); + /* TODO set ptr and size to something */ + return 0; + } + else + { + return EINTR; + } +} + +int svghmi_recv_dispatch(uint32_t size, char *ptr){ + printf("%%*s",size,ptr); + /* TODO something with ptr and size + - subscribe + or + - spread values + */ +} + diff -r 39d78c530cbb -r 35eeb1ed105f svghmi/svghmi.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/svghmi/svghmi.js Tue Sep 24 11:58:31 2019 +0200 @@ -0,0 +1,22 @@ +// svghmi.js + +(function(){ + // Open WebSocket to relative "/ws" address + var ws = new WebSocket(window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws')); + + // Register message reception handler + ws.onmessage = function (evt) { + // TODO : dispatch and cache hmi tree updates + + var received_msg = evt.data; + alert("Message is received..."+received_msg); + }; + + // Once connection established + ws.onopen = function (evt) { + // TODO : enable the HMI (was previously offline, or just starts) + // show main page + + ws.send("test"); + }; +})(); diff -r 39d78c530cbb -r 35eeb1ed105f svghmi/svghmi.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/svghmi/svghmi.py Tue Sep 24 11:58:31 2019 +0200 @@ -0,0 +1,401 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# This file is part of Beremiz +# Copyright (C) 2019: Edouard TISSERANT +# +# See COPYING file for copyrights details. + +from __future__ import absolute_import +import os +import shutil +from itertools import izip, imap +from pprint import pprint, pformat + +import wx + +import util.paths as paths +from POULibrary import POULibrary +from docutil import open_svg, get_inkscape_path +from lxml import etree + +from util.ProcessLogger import ProcessLogger +from runtime.typemapping import DebugTypesSize +import targets + +HMI_TYPES_DESC = { + "HMI_CLASS":{}, + "HMI_LABEL":{}, + "HMI_STRING":{}, + "HMI_INT":{}, + "HMI_REAL":{} +} + +HMI_TYPES = HMI_TYPES_DESC.keys() + +from XSLTransform import XSLTransform + +ScriptDirectory = paths.AbsDir(__file__) + +class HMITreeNode(object): + def __init__(self, path, name, nodetype, iectype = None, vartype = None): + self.path = path + self.name = name + self.nodetype = nodetype + + if iectype is not None: + self.iectype = iectype + self.vartype = vartype + if nodetype in ["HMI_LABEL", "HMI_ROOT"]: + self.children = [] + + def pprint(self, indent = 0): + res = ">"*indent + pformat(self.__dict__, indent = indent, depth = 1) + "\n" + if hasattr(self, "children"): + res += "\n".join([child.pprint(indent = indent + 1) + for child in self.children]) + res += "\n" + + return res + + def place_node(self, node): + best_child = None + known_best_match = 0 + for child in self.children : + if child.path is not None: + in_common = 0 + for child_path_item, node_path_item in izip(child.path, node.path): + if child_path_item == node_path_item: + in_common +=1 + else: + break + if in_common > known_best_match: + known_best_match = in_common + best_child = child + if best_child is not None and best_child.nodetype == "HMI_LABEL": + best_child.place_node(node) + else: + self.children.append(node) + + def etree(self): + + attribs = dict(name=self.name) + if self.path is not None: + attribs["path"]=".".join(self.path) + + res = etree.Element(self.nodetype, **attribs) + + if hasattr(self, "children"): + for child_etree in imap(lambda c:c.etree(), self.children): + res.append(child_etree) + + return res + + def traverse(self): + yield self + if hasattr(self, "children"): + for c in self.children: + for yoodl in c.traverse(): + yield yoodl + +# module scope for HMITree root +# so that CTN can use HMITree deduced in Library +# note: this only works because library's Generate_C is +# systematicaly invoked before CTN's CTNGenerate_C + +hmi_tree_root = None + +class SVGHMILibrary(POULibrary): + def GetLibraryPath(self): + return paths.AbsNeighbourFile(__file__, "pous.xml") + + def Generate_C(self, buildpath, varlist, IECCFLAGS): + global hmi_tree_root + + """ + PLC Instance Tree: + prog0 + +->v1 HMI_INT + +->v2 HMI_INT + +->fb0 (type mhoo) + | +->va HMI_LABEL + | +->v3 HMI_INT + | +->v4 HMI_INT + | + +->fb1 (type mhoo) + | +->va HMI_LABEL + | +->v3 HMI_INT + | +->v4 HMI_INT + | + +->fb2 + +->v5 HMI_IN + + HMI tree: + hmi0 + +->v1 + +->v2 + +->fb0_va + | +-> v3 + | +-> v4 + | + +->fb1_va + | +-> v3 + | +-> v4 + | + +->v5 + + """ + + # Filter known HMI types + hmi_types_instances = [v for v in varlist if v["derived"] in HMI_TYPES] + + hmi_tree_root = HMITreeNode(None, "/", "HMI_ROOT") + + # add special nodes + map(lambda (n,t): hmi_tree_root.children.append(HMITreeNode(None,n,t)), [ + ("plc_status", "HMI_PLC_STATUS"), + ("current_page", "HMI_CURRENT_PAGE")]) + + # deduce HMI tree from PLC HMI_* instances + for v in hmi_types_instances: + path = v["C_path"].split(".") + # ignores variables starting with _TMP_ + if path[-1].startswith("_TMP_"): + continue + new_node = HMITreeNode(path, path[-1], v["derived"], v["type"], v["vartype"]) + hmi_tree_root.place_node(new_node) + + variable_decl_array = [] + extern_variables_declarations = [] + buf_index = 0 + item_count = 0 + for node in hmi_tree_root.traverse(): + if hasattr(node, "iectype"): + sz = DebugTypesSize.get(node.iectype, 0) + variable_decl_array += [ + "{&(" + ".".join(node.path) + "), " + node.iectype + { + "EXT": "_P_ENUM", + "IN": "_P_ENUM", + "MEM": "_O_ENUM", + "OUT": "_O_ENUM", + "VAR": "_ENUM" + }[node.vartype] + ", " + + str(buf_index) + ", 0, }"] + buf_index += sz + item_count += 1 + if len(node.path) == 1: + extern_variables_declarations += [ + "extern __IEC_" + node.iectype + "_" + + "t" if node.vartype is "VAR" else "p" + + ".".join(node.path) + ";"] + + # TODO : filter only requiered external declarations + for v in varlist : + if v["C_path"].find('.') < 0 and v["vartype"] == "FB" : + extern_variables_declarations += [ + "extern %(type)s %(C_path)s;" % v] + + # TODO check if programs need to be declared separately + # "programs_declarations": "\n".join(["extern %(type)s %(C_path)s;" % + # p for p in self._ProgramList]), + + # C code to observe/access HMI tree variables + svghmi_c_filepath = paths.AbsNeighbourFile(__file__, "svghmi.c") + svghmi_c_file = open(svghmi_c_filepath, 'r') + svghmi_c_code = svghmi_c_file.read() + svghmi_c_file.close() + svghmi_c_code = svghmi_c_code % { + "variable_decl_array": ",\n".join(variable_decl_array), + "extern_variables_declarations": "\n".join(extern_variables_declarations), + "buffer_size": buf_index, + "item_count": item_count, + "var_access_code": targets.GetCode("var_access.c"), + "PLC_ticktime": self.GetCTR().GetTicktime() + } + + gen_svghmi_c_path = os.path.join(buildpath, "svghmi.c") + gen_svghmi_c = open(gen_svghmi_c_path, 'w') + gen_svghmi_c.write(svghmi_c_code) + gen_svghmi_c.close() + + # Python based WebSocket HMITree Server + svghmiserverfile = open(paths.AbsNeighbourFile(__file__, "svghmi_server.py"), 'r') + svghmiservercode = svghmiserverfile.read() + svghmiserverfile.close() + + runtimefile_path = os.path.join(buildpath, "runtime_svghmi.py") + runtimefile = open(runtimefile_path, 'w') + runtimefile.write(svghmiservercode) + runtimefile.close() + + return ((["svghmi"], [(gen_svghmi_c_path, IECCFLAGS)], True), "", + ("runtime_svghmi0.py", open(runtimefile_path, "rb"))) + +class SVGHMI(object): + XSD = """ + + + + + + + + + + """ + + ConfNodeMethods = [ + { + "bitmap": "ImportSVG", + "name": _("Import SVG"), + "tooltip": _("Import SVG"), + "method": "_ImportSVG" + }, + { + "bitmap": "ImportSVG", # should be something different + "name": _("Inkscape"), + "tooltip": _("Edit HMI"), + "method": "_StartInkscape" + }, + + # TODO : HMITree button + # - can drag'n'drop variabes to Inkscape + + ] + + def _getSVGpath(self, project_path=None): + if project_path is None: + project_path = self.CTNPath() + return os.path.join(project_path, "svghmi.svg") + + + def OnCTNSave(self, from_project_path=None): + if from_project_path is not None: + shutil.copyfile(self._getSVGpath(from_project_path), + self._getSVGpath()) + return True + + def GetSVGGeometry(self): + # invoke inskscape -S, csv-parse output, produce elements + InkscapeGeomColumns = ["Id", "x", "y", "w", "h"] + + inkpath = get_inkscape_path() + svgpath = self._getSVGpath() + _status, result, _err_result = ProcessLogger(None, + inkpath + " -S " + svgpath, + no_stdout=True, + no_stderr=True).spin() + res = [] + for line in result.split(): + strippedline = line.strip() + attrs = dict( + zip(InkscapeGeomColumns, line.strip().split(','))) + + res.append(etree.Element("bbox", **attrs)) + + return res + + def GetHMITree(self): + global hmi_tree_root + res = [hmi_tree_root.etree()] + return res + + def CTNGenerate_C(self, buildpath, locations): + """ + Return C code generated by iec2c compiler + when _generate_softPLC have been called + @param locations: ignored + @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND + """ + + location_str = "_".join(map(str, self.GetCurrentLocation())) + view_name = self.BaseParams.getName() + + svgfile = self._getSVGpath() + + res = ([], "", False) + + target_fname = "sghmi_"+location_str+".xhtml" + + target_path = os.path.join(self._getBuildPath(), target_fname) + target_file = open(target_path, 'w') + + if os.path.exists(svgfile): + + # TODO : move to __init__ + transform = XSLTransform(os.path.join(ScriptDirectory, "gen_index_xhtml.xslt"), + [("GetSVGGeometry", lambda *_ignored:self.GetSVGGeometry()), + ("GetHMITree", lambda *_ignored:self.GetHMITree())]) + + + # load svg as a DOM with Etree + svgdom = etree.parse(svgfile) + + # call xslt transform on Inkscape's SVG to generate XHTML + result = transform.transform(svgdom) + + result.write(target_file, encoding="utf-8") + # print(str(result)) + # print(transform.xslt.error_log) + + # TODO + # - Errors on HMI semantics + # - ... maybe something to have a global view of what is declared in SVG. + + else: + # TODO : use default svg that expose the HMI tree as-is + target_file.write(""" + + +

No SVG file provided

+ + +""") + + target_file.close() + + res += ((target_fname, open(target_path, "rb")),) + + runtimefile_path = os.path.join(buildpath, "runtime_svghmi1_%s.py" % location_str) + runtimefile = open(runtimefile_path, 'w') + runtimefile.write(""" +def _runtime_svghmi1_%(location)s_start(): + svghmi_root.putChild('%(view_name)s',File('%(xhtml)s')) + +def _runtime_svghmi1_%(location)s_stop(): + svghmi_root.delEntity('%(view_name)s') + + """ % {"location": location_str, + "xhtml": target_fname, + "view_name": view_name}) + + runtimefile.close() + + res += (("runtime_svghmi1_%s.py" % location_str, open(runtimefile_path, "rb")),) + + return res + + def _ImportSVG(self): + dialog = wx.FileDialog(self.GetCTRoot().AppFrame, _("Choose a SVG file"), os.getcwd(), "", _("SVG files (*.svg)|*.svg|All files|*.*"), wx.OPEN) + if dialog.ShowModal() == wx.ID_OK: + svgpath = dialog.GetPath() + if os.path.isfile(svgpath): + shutil.copy(svgpath, self._getSVGpath()) + else: + self.GetCTRoot().logger.write_error(_("No such SVG file: %s\n") % svgpath) + dialog.Destroy() + + def _StartInkscape(self): + svgfile = self._getSVGpath() + open_inkscape = True + if not self.GetCTRoot().CheckProjectPathPerm(): + dialog = wx.MessageDialog(self.GetCTRoot().AppFrame, + _("You don't have write permissions.\nOpen Inkscape anyway ?"), + _("Open Inkscape"), + wx.YES_NO | wx.ICON_QUESTION) + open_inkscape = dialog.ShowModal() == wx.ID_YES + dialog.Destroy() + if open_inkscape: + if not os.path.isfile(svgfile): + svgfile = None + open_svg(svgfile) diff -r 39d78c530cbb -r 35eeb1ed105f svghmi/svghmi_server.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/svghmi/svghmi_server.py Tue Sep 24 11:58:31 2019 +0200 @@ -0,0 +1,138 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# This file is part of Beremiz +# Copyright (C) 2019: Edouard TISSERANT +# See COPYING file for copyrights details. + +from __future__ import absolute_import + +from twisted.web.server import Site +from twisted.web.resource import Resource +from twisted.internet import reactor +from twisted.web.static import File + +from autobahn.twisted.websocket import WebSocketServerFactory, WebSocketServerProtocol +from autobahn.twisted.resource import WebSocketResource + +# TODO multiclient : +# session list lock +# svghmi_sessions = [] + +svghmi_session = None + +svghmi_send_collect = PLCBinary.svghmi_send_collect +svghmi_send_collect.restype = ctypes.c_int # error or 0 +svghmi_send_collect.argtypes = [ + ctypes.POINTER(ctypes.c_uint32), # size + ctypes.POINTER(ctypes.c_char_p)] # data ptr +# TODO multiclient : switch to arrays + +svghmi_recv_dispatch = PLCBinary.svghmi_recv_dispatch +svghmi_recv_dispatch.restype = ctypes.c_int # error or 0 +svghmi_recv_dispatch.argtypes = [ + ctypes.c_uint32, # size + ctypes.c_char_p] # data ptr +# TODO multiclient : switch to arrays + +class HMISession(object): + def __init__(self, protocol_instance): + global svghmi_session + + # TODO: kill existing session for robustness + assert(svghmi_session is None) + + svghmi_session = self + self.protocol_instance = protocol_instance + + # TODO multiclient : + # svghmi_sessions.append(self) + # get a unique bit index amont other svghmi_sessions, + # so that we can match flags passed by C->python callback + + def __del__(self): + global svghmi_session + assert(svghmi_session) + svghmi_session = None + + # TODO multiclient : + # svghmi_sessions.remove(self) + + def onMessage(self, msg): + # pass message to the C side recieve_message() + c_string = ctypes.c_char_p(msg) + c_string_pointer = ctypes.c_void_p(ctypes.addressof(c_string)) + svghmi_recv_dispatch(len(msg), msg) + + # TODO multiclient : pass client index as well + + + def sendMessage(self, msg): + self.sendMessage(msg, True) + +class HMIProtocol(WebSocketServerProtocol): + + def __init__(self, *args, **kwargs): + self._hmi_session = None + WebSocketServerProtocol.__init__(self, *args, **kwargs) + + def onOpen(self): + self._hmi_session = HMISession(self) + print "open" + + def onClose(self, wasClean, code, reason): + del self._hmi_session + self._hmi_session = None + print "close" + + def onMessage(self, msg, isBinary): + self._hmi_session.onMessage(msg) + print msg + #self.sendMessage(msg, binary) + +class HMIWebSocketServerFactory(WebSocketServerFactory): + protocol = HMIProtocol + +svghmi_root = None +svghmi_listener = None +svghmi_send_thread = None + +def SendThreadProc(): + global svghmi_session + size = ctypes.c_uint32() + ptr = ctypes.c_char_p() + res = 0 + while svghmi_send_collect(ctypes.byref(size), ctypes.byref(ptr)) == 0 and \ + svghmi_session is not None and \ + svghmi_session.sendMessage(ctypes.string_at(ptr,size)) == 0: + pass + + # TODO multiclient : dispatch to sessions + + + +# Called by PLCObject at start +def _runtime_svghmi0_start(): + global svghmi_listener, svghmi_root, svghmi_send_thread + + svghmi_root = Resource() + svghmi_root.putChild("ws", WebSocketResource(HMIWebSocketServerFactory())) + + svghmi_listener = reactor.listenTCP(8008, Site(svghmi_root)) + + # start a thread that call the C part of SVGHMI + svghmi_send_thread = Thread(target=SendThreadProc, name="SVGHMI Send") + svghmi_send_thread.start() + + +# Called by PLCObject at stop +def _runtime_svghmi0_stop(): + global svghmi_listener, svghmi_root, svghmi_send_thread + svghmi_root.delEntity("ws") + svghmi_root = None + svghmi_listener.stopListening() + svghmi_listener = None + # plc cleanup calls svghmi_(locstring)_cleanup and unlocks send thread + svghmi_send_thread.join() + svghmi_send_thread = None + diff -r 39d78c530cbb -r 35eeb1ed105f targets/Linux/plc_Linux_main_retain.c --- a/targets/Linux/plc_Linux_main_retain.c Tue Sep 24 11:55:59 2019 +0200 +++ b/targets/Linux/plc_Linux_main_retain.c Tue Sep 24 11:58:31 2019 +0200 @@ -105,20 +105,20 @@ /* Compare current hash with hash from file byte by byte. */ int CheckFilehash(void) { - int k; + int k,ret; int offset = sizeof(retain_info.retain_size); rewind(retain_buffer); fseek(retain_buffer, offset , SEEK_SET); uint32_t size; - fread(&size, sizeof(size), 1, retain_buffer); + ret = fread(&size, sizeof(size), 1, retain_buffer); if (size != retain_info.hash_size) return 0; for(k = 0; k < retain_info.hash_size; k++){ uint8_t file_digit; - fread(&file_digit, sizeof(char), 1, retain_buffer); + ret = fread(&file_digit, sizeof(char), 1, retain_buffer); if (file_digit != *(retain_info.hash+k)) return 0; } @@ -317,8 +317,9 @@ void Remind(unsigned int offset, unsigned int count, void *p) { + int ret; /* Remind variable from file. */ fseek(retain_buffer, retain_info.header_offset+offset, SEEK_SET); - fread((void *)p, count, 1, retain_buffer); + ret = fread((void *)p, count, 1, retain_buffer); } #endif // !HAVE_RETAIN diff -r 39d78c530cbb -r 35eeb1ed105f targets/plc_debug.c --- a/targets/plc_debug.c Tue Sep 24 11:55:59 2019 +0200 +++ b/targets/plc_debug.c Tue Sep 24 11:58:31 2019 +0200 @@ -71,37 +71,9 @@ } } -#define __Unpack_case_t(TYPENAME) \ - case TYPENAME##_ENUM :\ - *flags = ((__IEC_##TYPENAME##_t *)varp)->flags;\ - forced_value_p = *real_value_p = &((__IEC_##TYPENAME##_t *)varp)->value;\ - break; - -#define __Unpack_case_p(TYPENAME)\ - case TYPENAME##_O_ENUM :\ - *flags = __IEC_OUTPUT_FLAG;\ - case TYPENAME##_P_ENUM :\ - *flags |= ((__IEC_##TYPENAME##_p *)varp)->flags;\ - *real_value_p = ((__IEC_##TYPENAME##_p *)varp)->value;\ - forced_value_p = &((__IEC_##TYPENAME##_p *)varp)->fvalue;\ - break; - -void* UnpackVar(dbgvardsc_t *dsc, void **real_value_p, char *flags) -{ - void *varp = dsc->ptr; - void *forced_value_p = NULL; - *flags = 0; - /* find data to copy*/ - switch(dsc->type){ - __ANY(__Unpack_case_t) - __ANY(__Unpack_case_p) - default: - break; - } - if (*flags & __IEC_FORCE_FLAG) - return forced_value_p; - return *real_value_p; -} +#define __Unpack_desc_type dbgvardsc_t + +%(var_access_code)s void Remind(unsigned int offset, unsigned int count, void * p); diff -r 39d78c530cbb -r 35eeb1ed105f targets/var_access.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/targets/var_access.c Tue Sep 24 11:58:31 2019 +0200 @@ -0,0 +1,33 @@ + +#define __Unpack_case_t(TYPENAME) \ + case TYPENAME##_ENUM :\ + *flags = ((__IEC_##TYPENAME##_t *)varp)->flags;\ + forced_value_p = *real_value_p = &((__IEC_##TYPENAME##_t *)varp)->value;\ + break; + +#define __Unpack_case_p(TYPENAME)\ + case TYPENAME##_O_ENUM :\ + *flags = __IEC_OUTPUT_FLAG;\ + case TYPENAME##_P_ENUM :\ + *flags |= ((__IEC_##TYPENAME##_p *)varp)->flags;\ + *real_value_p = ((__IEC_##TYPENAME##_p *)varp)->value;\ + forced_value_p = &((__IEC_##TYPENAME##_p *)varp)->fvalue;\ + break; + +static void* UnpackVar(__Unpack_desc_type *dsc, void **real_value_p, char *flags) +{ + void *varp = dsc->ptr; + void *forced_value_p = NULL; + *flags = 0; + /* find data to copy*/ + switch(dsc->type){ + __ANY(__Unpack_case_t) + __ANY(__Unpack_case_p) + default: + break; + } + if (*flags & __IEC_FORCE_FLAG) + return forced_value_p; + return *real_value_p; +} + diff -r 39d78c530cbb -r 35eeb1ed105f tests/svghmi/beremiz.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/svghmi/beremiz.xml Tue Sep 24 11:58:31 2019 +0200 @@ -0,0 +1,5 @@ + + + + + diff -r 39d78c530cbb -r 35eeb1ed105f tests/svghmi/plc.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/svghmi/plc.xml Tue Sep 24 11:58:31 2019 +0200 @@ -0,0 +1,241 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TargetPressure + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + TargetPressure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Pressure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 23 + + + + + + + + + + + + + AddOut + + + + + + + + + + + + + + + + + + diff -r 39d78c530cbb -r 35eeb1ed105f tests/svghmi/svghmi_0@svghmi/baseconfnode.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/svghmi/svghmi_0@svghmi/baseconfnode.xml Tue Sep 24 11:58:31 2019 +0200 @@ -0,0 +1,2 @@ + + diff -r 39d78c530cbb -r 35eeb1ed105f tests/svghmi/svghmi_0@svghmi/confnode.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/svghmi/svghmi_0@svghmi/confnode.xml Tue Sep 24 11:58:31 2019 +0200 @@ -0,0 +1,2 @@ + + diff -r 39d78c530cbb -r 35eeb1ed105f tests/svghmi/svghmi_0@svghmi/svghmi.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/svghmi/svghmi_0@svghmi/svghmi.svg Tue Sep 24 11:58:31 2019 +0200 @@ -0,0 +1,117 @@ + + + + + + + + image/svg+xml + + + + + + + + + =HMI= +role = Page; +name = "Home"; + + + =HMI= +role = Input; +path = "/PRESSURETARGET"; +/* Don't forget to press the "Set" button ! */8888 + =HMI= +role = Display; +path = "/PUMP/PRESSURE"; +/* Don't forget to press the "Set" button ! */8888 + SetPoint + Actual + diff -r 39d78c530cbb -r 35eeb1ed105f yslt_noindent.yml2 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/yslt_noindent.yml2 Tue Sep 24 11:58:31 2019 +0200 @@ -0,0 +1,40 @@ +include yslt.yml2 +!! +def indent(level): + return "" +!! + +in xsl { + decl istylesheet ( + *output="xml", + version="1.0", + xmlns:xsl="http://www.w3.org/1999/XSL/Transform", + xmlns:exsl='http://exslt.org/common', + xmlns:regexp="http://exslt.org/regular-expressions", + xmlns:str="http://exslt.org/strings", + extension-element-prefixes='exsl regexp str' + ) alias stylesheet { + output *output; + content; + }; + + decl indent() alias -; + + decl template(match) { + content; + }; + + decl function(name) alias template { + content; + }; + + decl call(name) alias call-template { + content; + }; + + decl apply(select) alias apply-templates { + content; + }; +} + +