# HG changeset patch
# User Edouard Tisserant
# Date 1677573591 -3600
# Node ID 0c06de0a39b53dcebcc5d24319df4f08171af8f6
# Parent 57bcc9545ca1896e618ae4cbf69b6425168aea64# Parent ed1ec3136c2b4ee899384d9228cdf9050326ef15
Merge.
diff -r ed1ec3136c2b -r 0c06de0a39b5 Beremiz_service.py
--- a/Beremiz_service.py Mon Feb 27 13:47:36 2023 +0100
+++ b/Beremiz_service.py Tue Feb 28 09:39:51 2023 +0100
@@ -185,6 +185,12 @@
return os.path.join(beremiz_dir, *args)
+import locale
+# Matiec's standard library relies on libC's locale-dependent
+# string to/from number convertions, but IEC-61131 counts
+# on '.' for decimal point. Therefore locale is reset to "C" */
+locale.setlocale(locale.LC_NUMERIC, "C")
+
def SetupI18n():
# Get folder containing translation files
localedir = os.path.join(beremiz_dir, "locale")
@@ -205,7 +211,6 @@
# Define locale domain
loc.AddCatalog(domain)
- import locale
global default_locale
default_locale = locale.getdefaultlocale()[1]
diff -r ed1ec3136c2b -r 0c06de0a39b5 PLCControler.py
--- a/PLCControler.py Mon Feb 27 13:47:36 2023 +0100
+++ b/PLCControler.py Tue Feb 28 09:39:51 2023 +0100
@@ -448,12 +448,12 @@
return len(self.GetInstanceList(pou_infos, name, debug)) > 0
return False
- def GenerateProgram(self, filepath=None):
+ def GenerateProgram(self, filepath=None, **kwargs):
errors = []
warnings = []
if self.Project is not None:
try:
- self.ProgramChunks = GenerateCurrentProgram(self, self.Project, errors, warnings)
+ self.ProgramChunks = GenerateCurrentProgram(self, self.Project, errors, warnings,**kwargs)
self.NextCompiledProject = self.Copy(self.Project)
program_text = "".join([item[0] for item in self.ProgramChunks])
if filepath is not None:
@@ -1147,28 +1147,35 @@
def GetConfigurationExtraVariables(self):
global_vars = []
- for var_name, var_type, var_initial in self.GetConfNodeGlobalInstances():
- tempvar = PLCOpenParser.CreateElement("variable", "globalVars")
- tempvar.setname(var_name)
-
- tempvartype = PLCOpenParser.CreateElement("type", "variable")
- if var_type in self.GetBaseTypes():
- tempvartype.setcontent(PLCOpenParser.CreateElement(
- var_type.lower()
- if var_type in ["STRING", "WSTRING"]
- else var_type, "dataType"))
+ for global_instance in self.GetConfNodeGlobalInstances():
+ if type(global_instance)==tuple:
+ # usual global without modifier from a CTN or a library
+ var_name, var_type, var_initial = global_instance
+ tempvar = PLCOpenParser.CreateElement("variable", "globalVars")
+ tempvar.setname(var_name)
+
+ tempvartype = PLCOpenParser.CreateElement("type", "variable")
+ if var_type in self.GetBaseTypes():
+ tempvartype.setcontent(PLCOpenParser.CreateElement(
+ var_type.lower()
+ if var_type in ["STRING", "WSTRING"]
+ else var_type, "dataType"))
+ else:
+ tempderivedtype = PLCOpenParser.CreateElement("derived", "dataType")
+ tempderivedtype.setname(var_type)
+ tempvartype.setcontent(tempderivedtype)
+ tempvar.settype(tempvartype)
+
+ if var_initial != "":
+ value = PLCOpenParser.CreateElement("initialValue", "variable")
+ value.setvalue(var_initial)
+ tempvar.setinitialValue(value)
+
+ global_vars.append(tempvar)
else:
- tempderivedtype = PLCOpenParser.CreateElement("derived", "dataType")
- tempderivedtype.setname(var_type)
- tempvartype.setcontent(tempderivedtype)
- tempvar.settype(tempvartype)
-
- if var_initial != "":
- value = PLCOpenParser.CreateElement("initialValue", "variable")
- value.setvalue(var_initial)
- tempvar.setinitialValue(value)
-
- global_vars.append(tempvar)
+ # case of varlists from a TC6 library
+ global_vars.append(global_instance)
+
return global_vars
# Function that returns the block definition associated to the block type given
diff -r ed1ec3136c2b -r 0c06de0a39b5 PLCGenerator.py
--- a/PLCGenerator.py Mon Feb 27 13:47:36 2023 +0100
+++ b/PLCGenerator.py Tue Feb 28 09:39:51 2023 +0100
@@ -277,21 +277,27 @@
("\n", ())]
var_number = 0
- varlists = [(varlist, varlist.getvariable()[:]) for varlist in configuration.getglobalVars()]
+ varlists = configuration.getglobalVars()[:]
extra_variables = self.Controler.GetConfigurationExtraVariables()
- extra_global_vars = None
- if len(extra_variables) > 0 and len(varlists) == 0:
- extra_global_vars = PLCOpenParser.CreateElement("globalVars", "interface")
- configuration.setglobalVars([extra_global_vars])
- varlists = [(extra_global_vars, [])]
-
- for variable in extra_variables:
- varlists[-1][0].appendvariable(variable)
- varlists[-1][1].append(variable)
+ extra_CTN_globals = []
+
+ for item in extra_variables:
+ if item.getLocalTag() == "globalVars":
+ varlists.append(item)
+ else:
+ extra_CTN_globals.append(item)
+
+ if len(extra_CTN_globals) > 0:
+ extra_varlist = PLCOpenParser.CreateElement("globalVars", "interface")
+
+ for variable in extra_CTN_globals:
+ extra_varlist.appendvariable(variable)
+
+ varlists.append(extra_varlist)
# Generate any global variable in configuration
- for varlist, varlist_variables in varlists:
+ for varlist in varlists:
variable_type = errorVarTypes.get("VAR_GLOBAL", "var_local")
# Generate variable block with modifier
config += [(" VAR_GLOBAL", ())]
@@ -303,7 +309,7 @@
config += [(" NON_RETAIN", (tagname, variable_type, (var_number, var_number + len(varlist.getvariable())), "non_retain"))]
config += [("\n", ())]
# Generate any variable of this block
- for var in varlist_variables:
+ for var in varlist.getvariable():
vartype_content = var.gettype().getcontent()
if vartype_content.getLocalTag() == "derived":
var_type = vartype_content.getname()
@@ -331,12 +337,6 @@
var_number += 1
config += [(" END_VAR\n", ())]
- if extra_global_vars is not None:
- configuration.remove(extra_global_vars)
- else:
- for variable in extra_variables:
- varlists[-1][0].remove(variable)
-
# Generate any resource in the configuration
for resource in configuration.getresource():
config += self.GenerateResource(resource, configuration.getname())
@@ -458,7 +458,7 @@
return resrce
# Generate the entire program for current project
- def GenerateProgram(self, log):
+ def GenerateProgram(self, log, noconfig=False):
log("Collecting data types")
# Find all data types defined
for datatype in self.Project.getdataTypes():
@@ -480,6 +480,8 @@
for pou_name in self.PouComputed.keys():
log("Generate POU %s"%pou_name)
self.GeneratePouProgram(pou_name)
+ if noconfig:
+ return
# Generate every configurations defined
log("Generate Config(s)")
for config in self.Project.getconfigurations():
@@ -1765,7 +1767,7 @@
return program
-def GenerateCurrentProgram(controler, project, errors, warnings):
+def GenerateCurrentProgram(controler, project, errors, warnings, **kwargs):
generator = ProgramGenerator(controler, project, errors, warnings)
if hasattr(controler, "logger"):
def log(txt):
@@ -1774,5 +1776,5 @@
def log(txt):
pass
- generator.GenerateProgram(log)
+ generator.GenerateProgram(log,**kwargs)
return generator.GetGeneratedProgram()
diff -r ed1ec3136c2b -r 0c06de0a39b5 POULibrary.py
--- a/POULibrary.py Mon Feb 27 13:47:36 2023 +0100
+++ b/POULibrary.py Tue Feb 28 09:39:51 2023 +0100
@@ -44,7 +44,7 @@
def GetSTCode(self):
if not self.program:
- self.program = self.LibraryControler.GenerateProgram()[0]+"\n"
+ self.program = self.LibraryControler.GenerateProgram(noconfig=True)[0]+"\n"
return self.program
def GetName(self):
@@ -65,9 +65,14 @@
def GlobalInstances(self):
"""
- @return: [(instance_name, instance_type),...]
+ @return: [varlist_object, ...]
"""
- return []
+ varlists = []
+ for configuration in self.LibraryControler.Project.getconfigurations():
+ varlist = configuration.getglobalVars()
+ if len(varlist)>0 :
+ varlists += varlist
+ return varlists
def FatalError(self, message):
""" Raise an exception that will trigger error message intended to
diff -r ed1ec3136c2b -r 0c06de0a39b5 editors/TextViewer.py
--- a/editors/TextViewer.py Mon Feb 27 13:47:36 2023 +0100
+++ b/editors/TextViewer.py Tue Feb 28 09:39:51 2023 +0100
@@ -130,8 +130,7 @@
self.Editor.SetUseTabs(0)
self.Editor.SetModEventMask(wx.stc.STC_MOD_BEFOREINSERT |
- wx.stc.STC_MOD_BEFOREDELETE |
- wx.stc.STC_PERFORMED_USER)
+ wx.stc.STC_MOD_BEFOREDELETE)
self.Bind(wx.stc.EVT_STC_STYLENEEDED, self.OnStyleNeeded, self.Editor)
self.Editor.Bind(wx.stc.EVT_STC_MARGINCLICK, self.OnMarginClick)
@@ -213,25 +212,24 @@
self.SearchResults = None
self.CurrentFindHighlight = None
+ Buffering = "Off"
def OnModification(self, event):
if not self.DisableEvents:
mod_type = event.GetModificationType()
if mod_type & wx.stc.STC_MOD_BEFOREINSERT:
if self.CurrentAction is None:
- self.StartBuffering()
+ self.Buffering = "ShouldStart"
elif self.CurrentAction[0] != "Add" or self.CurrentAction[1] != event.GetPosition() - 1:
- self.Controler.EndBuffering()
- self.StartBuffering()
+ self.Buffering = "ShouldRestart"
self.CurrentAction = ("Add", event.GetPosition())
- wx.CallAfter(self.RefreshModel)
+ self.RefreshModelAfter()
elif mod_type & wx.stc.STC_MOD_BEFOREDELETE:
if self.CurrentAction is None:
- self.StartBuffering()
+ self.Buffering = "ShouldStart"
elif self.CurrentAction[0] != "Delete" or self.CurrentAction[1] != event.GetPosition() + 1:
- self.Controler.EndBuffering()
- self.StartBuffering()
+ self.Buffering = "ShouldRestart"
self.CurrentAction = ("Delete", event.GetPosition())
- wx.CallAfter(self.RefreshModel)
+ self.RefreshModelAfter()
event.Skip()
def OnDoDrop(self, event):
@@ -379,7 +377,7 @@
elif values[3] == self.TagName:
self.ResetBuffer()
event.SetDragText(values[0])
- wx.CallAfter(self.RefreshModel)
+ self.RefreshModelAfter()
else:
message = _("Variable don't belong to this POU!")
if message is not None:
@@ -429,10 +427,14 @@
self.ParentWindow.RefreshFileMenu()
self.ParentWindow.RefreshEditMenu()
+ def EndBuffering(self):
+ self.Controler.EndBuffering()
+
def ResetBuffer(self):
if self.CurrentAction is not None:
- self.Controler.EndBuffering()
+ self.EndBuffering()
self.CurrentAction = None
+ self.Buffering == "Off"
def GetBufferState(self):
if not self.Debug and self.TextSyntax != "ALL":
@@ -834,12 +836,29 @@
self.RemoveHighlight(*self.CurrentFindHighlight)
self.CurrentFindHighlight = None
+ pending_model_refresh=False
def RefreshModel(self):
+ self.pending_model_refresh=False
self.RefreshJumpList()
self.Colourise(0, -1)
+
+ if self.Buffering == "ShouldStart":
+ self.StartBuffering()
+ self.Buffering == "On"
+ elif self.Buffering == "ShouldRestart":
+ self.EndBuffering()
+ self.StartBuffering()
+ self.Buffering == "On"
+
self.Controler.SetEditedElementText(self.TagName, self.GetText())
self.ResetSearchResults()
+ def RefreshModelAfter(self):
+ if self.pending_model_refresh:
+ return
+ self.pending_model_refresh=True
+ wx.CallAfter(self.RefreshModel)
+
def OnKeyDown(self, event):
key = event.GetKeyCode()
if self.Controler is not None:
diff -r ed1ec3136c2b -r 0c06de0a39b5 exemples/csv_read/beremiz.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/exemples/csv_read/beremiz.xml Tue Feb 28 09:39:51 2023 +0100
@@ -0,0 +1,5 @@
+
+
+
+
+
diff -r ed1ec3136c2b -r 0c06de0a39b5 exemples/csv_read/plc.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/exemples/csv_read/plc.xml Tue Feb 28 09:39:51 2023 +0100
@@ -0,0 +1,341 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ CSV_NAME_0
+
+
+
+
+
+
+ CSV_ROWIDX
+
+
+
+
+
+
+ CSV_COLIDX
+
+
+
+
+
+
+ CSV_NAME_1
+
+
+
+
+
+
+ CSV_ROWSTR
+
+
+
+
+
+
+ CSV_COLSTR
+
+
+
+
+
+
+
+
+
+
+ CSV_RES_0
+
+
+
+
+
+
+
+
+
+
+ CSV_ACK_0
+
+
+
+
+
+
+
+
+
+
+ CSV_ACK_1
+
+
+
+
+
+
+
+
+
+
+ CSV_RES_1
+
+
+
+
+
+
+ CSV_RELOAD_BTN
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff -r ed1ec3136c2b -r 0c06de0a39b5 exemples/csv_read/project_files/my_int_csv.csv
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/exemples/csv_read/project_files/my_int_csv.csv Tue Feb 28 09:39:51 2023 +0100
@@ -0,0 +1,15 @@
+1,1.01,1.02,1.03,1.04,1.05,1.06
+1.1,1.11,1.12,1.13,1.14,1.15,1.16
+1.2,1.21,1.22,1.23,1.24,1.25,1.26
+1.3,1.31,1.32,1.33,1.34,1.35,1.36
+1.4,1.41,1.42,1.43,1.44,1.45,1.46
+1.5,1.51,1.52,1.53,1.54,1.55,1.56
+1.6,1.61,1.62,1.63,1.64,1.65,1.66
+1.7,1.71,1.72,1.73,1.74,1.75,1.76
+1.8,1.81,1.82,1.83,1.84,1.85,1.86
+1.9,1.91,1.92,1.93,1.94,1.95,1.96
+2,2.01,2.02,2.03,2.04,2.05,2.06
+2.1,2.11,2.12,2.13,2.14,2.15,2.16
+2.2,2.21,2.22,2.23,2.24,2.25,2.26
+2.3,2.31,2.32,2.33,2.34,2.35,2.36
+2.4,2.41,2.42,2.43,2.44,2.45,2.46
diff -r ed1ec3136c2b -r 0c06de0a39b5 exemples/csv_read/project_files/my_int_csv.ods
Binary file exemples/csv_read/project_files/my_int_csv.ods has changed
diff -r ed1ec3136c2b -r 0c06de0a39b5 exemples/csv_read/project_files/my_str_csv.csv
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/exemples/csv_read/project_files/my_str_csv.csv Tue Feb 28 09:39:51 2023 +0100
@@ -0,0 +1,8 @@
+Title,Ingredient A,Ingredient B,Ingredient C,Ingredient D,Ingredient E,Ingredient F,Ingredient G
+Recipe 1,1,2,3,4,5,6,7
+Recipe 2,2,3,4,5,6,7,8
+Recipe 3,3,4,5,6,7,8,9
+Recipe 4,4,5,6,7,8,9,10
+Recipe 5,5,6,7,8,9,10,11
+Recipe 6,6,7,8,9,10,11,12
+Recipe 7,7,8,9,10,11,12,13
diff -r ed1ec3136c2b -r 0c06de0a39b5 exemples/csv_read/project_files/my_str_csv.ods
Binary file exemples/csv_read/project_files/my_str_csv.ods has changed
diff -r ed1ec3136c2b -r 0c06de0a39b5 exemples/csv_read/svghmi_0@svghmi/baseconfnode.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/exemples/csv_read/svghmi_0@svghmi/baseconfnode.xml Tue Feb 28 09:39:51 2023 +0100
@@ -0,0 +1,2 @@
+
+
diff -r ed1ec3136c2b -r 0c06de0a39b5 exemples/csv_read/svghmi_0@svghmi/confnode.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/exemples/csv_read/svghmi_0@svghmi/confnode.xml Tue Feb 28 09:39:51 2023 +0100
@@ -0,0 +1,2 @@
+
+
diff -r ed1ec3136c2b -r 0c06de0a39b5 exemples/csv_read/svghmi_0@svghmi/svghmi.svg
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/exemples/csv_read/svghmi_0@svghmi/svghmi.svg Tue Feb 28 09:39:51 2023 +0100
@@ -0,0 +1,1787 @@
+
+
+
+
diff -r ed1ec3136c2b -r 0c06de0a39b5 exemples/svghmi_jumps/svghmi_0@svghmi/svghmi.svg
--- a/exemples/svghmi_jumps/svghmi_0@svghmi/svghmi.svg Mon Feb 27 13:47:36 2023 +0100
+++ b/exemples/svghmi_jumps/svghmi_0@svghmi/svghmi.svg Tue Feb 28 09:39:51 2023 +0100
@@ -25,7 +25,7 @@
image/svg+xml
-
+
@@ -40,17 +40,17 @@
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
- inkscape:window-width="1600"
- inkscape:window-height="836"
+ inkscape:window-width="1850"
+ inkscape:window-height="1036"
id="namedview4"
showgrid="false"
- inkscape:zoom="0.23177389"
- inkscape:cx="1999.5317"
- inkscape:cy="-682.74047"
+ inkscape:zoom="0.46354778"
+ inkscape:cx="-544.27948"
+ inkscape:cy="655.56978"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1"
- inkscape:current-layer="hmi0"
+ inkscape:current-layer="g2496"
showguides="true"
inkscape:guide-bbox="true"
borderlayer="true"
@@ -67,7 +67,7 @@
transform="translate(1320,1520)"
width="100%"
height="100%"
- inkscape:label="HMI:Page:RelativePage@/FB_ZERO" />
+ inkscape:label="HMI:Page:RelativePage:p=6@p=page_number@/FB_ZERO" />
+ inkscape:label="HMI:Page:Relative:p=5@p=page_number" />
HMI:Jump:RelativePage
+ 0
+ transform="translate(620.54487,-11.353461)">
+ transform="translate(4.2410198,-11.353461)">
- Press Ctrl+X to edit SVG elements directly with XML editor
+
+ declaration of user_level HMI local variable(not a PLC variable)
+
diff -r ed1ec3136c2b -r 0c06de0a39b5 py_ext/pous.xml
--- a/py_ext/pous.xml Mon Feb 27 13:47:36 2023 +0100
+++ b/py_ext/pous.xml Tue Feb 28 09:39:51 2023 +0100
@@ -1,10 +1,10 @@
-
+
-
+
@@ -17,6 +17,1646 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'CSVRdStr("'
+
+
+
+
+
+
+ FILE_NAME
+
+
+
+
+
+
+ '","'
+
+
+
+
+
+
+ ROW
+
+
+
+
+
+
+ '","'
+
+
+
+
+
+
+ COLUMN
+
+
+
+
+
+
+ '")'
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ OLDCODE
+
+
+
+
+
+
+ OLDCODE
+
+
+
+
+
+
+ OLDCODE
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ pyext_csv_update
+
+
+
+
+
+
+
+
+
+
+ ACK
+
+
+
+
+
+
+
+
+
+
+
+
+ RESULT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+
+
+
+
+ '#'
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ TRIG
+
+
+
+
+
+
+
+
+
+
+ ACK
+
+
+
+
+
+
+
+
+
+
+ RESULT
+
+
+
+
+
+
+ 'pyext_csv_reload()'
+
+
+
+
+
+
+
+
+
+
+ pyext_csv_update
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'CSVRdInt("'
+
+
+
+
+
+
+ FILE_NAME
+
+
+
+
+
+
+ '",'
+
+
+
+
+
+
+ ROW
+
+
+
+
+
+
+ ','
+
+
+
+
+
+
+ COLUMN
+
+
+
+
+
+
+ ')'
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ OLDCODE
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ OLDCODE
+
+
+
+
+
+
+ OLDCODE
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ pyext_csv_update
+
+
+
+
+
+
+
+
+
+
+
+
+ ACK
+
+
+
+
+
+
+
+
+
+
+
+
+ RESULT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+
+
+
+
+ '#'
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -445,6 +2085,19 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff -r ed1ec3136c2b -r 0c06de0a39b5 py_ext/py_ext.py
--- a/py_ext/py_ext.py Mon Feb 27 13:47:36 2023 +0100
+++ b/py_ext/py_ext.py Tue Feb 28 09:39:51 2023 +0100
@@ -30,6 +30,98 @@
from py_ext.PythonFileCTNMixin import PythonFileCTNMixin
import util.paths as paths
+pyext_python_lib_code = """
+
+import csv
+from collections import OrderedDict
+
+csv_int_files = {}
+def CSVRdInt(fname, rowidx, colidx):
+ \"\"\"
+ Return value at row/column pointed by integer indexes
+ Assumes data starts at first row and first column, no headers.
+ \"\"\"
+ global csv_int_files
+ data = csv_int_files.get(fname, None)
+ if data is None:
+ data = list()
+ try:
+ csvfile = open(fname, 'rb')
+ except IOError:
+ return "#FILE_NOT_FOUND"
+ try:
+ dialect = csv.Sniffer().sniff(csvfile.read(1024))
+ csvfile.seek(0)
+ reader = csv.reader(csvfile, dialect)
+ for row in reader:
+ data.append(row)
+ except csv.Error:
+ return "#CSV_ERROR"
+ finally:
+ csvfile.close()
+ csv_int_files[fname] = data
+
+ try:
+ row = data[rowidx]
+ except IndexError:
+ return "#ROW_NOT_FOUND"
+
+ try:
+ return row[colidx]
+ except IndexError:
+ return "#COL_NOT_FOUND"
+
+
+csv_str_files = {}
+def CSVRdStr(fname, rowname, colname):
+ \"\"\"
+ Return value at row/column pointed by a pair of names as string
+ Assumes first row is column headers and first column is row name.
+ \"\"\"
+ global csv_str_files
+ entry = csv_str_files.get(fname, None)
+ if entry is None:
+ data = dict()
+ try:
+ csvfile = open(fname, 'rb')
+ except IOError:
+ return "#FILE_NOT_FOUND"
+ try:
+ dialect = csv.Sniffer().sniff(csvfile.read(1024))
+ csvfile.seek(0)
+ reader = csv.reader(csvfile, dialect)
+ headers = dict([(name, index) for index, name in enumerate(reader.next()[1:])])
+ for row in reader:
+ data[row[0]] = row[1:]
+ except csv.Error:
+ return "#CSV_ERROR"
+ finally:
+ csvfile.close()
+ csv_str_files[fname] = (headers, data)
+ else:
+ headers, data = entry
+
+ try:
+ row = data[rowname]
+ except KeyError:
+ return "#ROW_NOT_FOUND"
+
+ try:
+ colidx = headers[colname]
+ except KeyError:
+ return "#COL_NOT_FOUND"
+
+ try:
+ return row[colidx]
+ except IndexError:
+ return "#COL_NOT_FOUND"
+
+def pyext_csv_reload():
+ global csv_int_files, csv_str_files
+ csv_int_files.clear()
+ csv_str_files.clear()
+
+"""
class PythonLibrary(POULibrary):
def GetLibraryPath(self):
@@ -57,7 +149,13 @@
pythonfile.write(plc_python_code)
pythonfile.close()
- return (["py_ext"], [(Gen_Pythonfile_path, IECCFLAGS)], True), ""
+ runtimefile_path = os.path.join(buildpath, "runtime_00_pyext.py")
+ runtimefile = open(runtimefile_path, 'w')
+ runtimefile.write(pyext_python_lib_code)
+ runtimefile.close()
+ return ((["py_ext"], [(Gen_Pythonfile_path, IECCFLAGS)], True), "",
+ ("runtime_00_pyext.py", open(runtimefile_path, "rb")))
+
class PythonFile(PythonFileCTNMixin):
diff -r ed1ec3136c2b -r 0c06de0a39b5 runtime/NevowServer.py
--- a/runtime/NevowServer.py Mon Feb 27 13:47:36 2023 +0100
+++ b/runtime/NevowServer.py Tue Feb 28 09:39:51 2023 +0100
@@ -27,6 +27,7 @@
from __future__ import print_function
import os
import collections
+import shutil
import platform as platform_module
from zope.interface import implements
from nevow import appserver, inevow, tags, loaders, athena, url, rend
@@ -230,6 +231,18 @@
"Restart or Repair"),
action=_("Do"))
+ # pylint: disable=no-self-argument
+ def uploadFile(
+ ctx=annotate.Context(),
+ uploadedfile=annotate.FileUpload(required=True,
+ label=_("File to upload"))):
+ pass
+
+ uploadFile = annotate.autocallable(uploadFile,
+ label=_(
+ "Upload a file to PLC working directory"),
+ action=_("Upload"))
+
customSettingsURLs = {
}
@@ -304,8 +317,14 @@
GetPLCObjectSingleton().RepairPLC()
else:
MainWorker.quit()
-
-
+
+ def uploadFile(self, uploadedfile, **kwargs):
+ if uploadedfile is not None:
+ fobj = getattr(uploadedfile, "file", None)
+ if fobj is not None:
+ with open(uploadedfile.filename, 'w') as destfd:
+ fobj.seek(0)
+ shutil.copyfileobj(fobj,destfd)
def locateChild(self, ctx, segments):
if segments[0] in customSettingsURLs:
diff -r ed1ec3136c2b -r 0c06de0a39b5 svghmi/analyse_widget.xslt
--- a/svghmi/analyse_widget.xslt Mon Feb 27 13:47:36 2023 +0100
+++ b/svghmi/analyse_widget.xslt Tue Feb 28 09:39:51 2023 +0100
@@ -854,6 +854,46 @@
Value to display
+
+
+
+
+
+
+
+ Arguments are either:
+
+
+
+ - XXX reference path TODO
+
+
+
+ - name=value: setting variable with literal value.
+
+ - name=other_name: copy variable content into another
+
+
+
+ "active"+"inactive" labeled elements can be provided to show feedback when pressed
+
+
+
+ Exemples:
+
+
+
+ HMI:Page:notify=1@notify=/PLCVAR
+
+ HMI:Page:ack=2:notify=1@ack=.local_var@notify=/PLCVAR
+
+
+
+
+
+ Page
+
+
diff -r ed1ec3136c2b -r 0c06de0a39b5 svghmi/detachable_pages.ysl2
--- a/svghmi/detachable_pages.ysl2 Mon Feb 27 13:47:36 2023 +0100
+++ b/svghmi/detachable_pages.ysl2 Tue Feb 28 09:39:51 2023 +0100
@@ -137,6 +137,10 @@
const "_detachable_elements", "func:detachable_elements($hmi_pages | $keypads)";
const "detachable_elements", "$_detachable_elements[not(ancestor::*/@id = $_detachable_elements/@id)]";
+emit "declarations:page-class" {
+ | class PageWidget extends Widget{}
+}
+
emit "declarations:detachable-elements" {
|
| var detachable_elements = {
@@ -165,8 +169,13 @@
const "all_page_widgets","$hmi_widgets[@id = $page_all_elements/@id and @id != $page/@id]";
const "page_managed_widgets","$all_page_widgets[not(@id=$in_forEach_widget_ids)]";
+
+ const "page_root_path", "$desc/path[not(@assign)]";
+ if "count($page_root_path)>1"
+ error > Page id="«$page/@id»" : only one root path can be declared
+
const "page_relative_widgets",
- "$page_managed_widgets[func:is_descendant_path(func:widget(@id)/path/@value, $desc/path/@value)]";
+ "$page_managed_widgets[func:is_descendant_path(func:widget(@id)/path/@value, $page_root_path/@value)]";
// Take closest ancestor in detachable_elements
// since nested detachable elements are filtered out
@@ -178,19 +187,19 @@
ancestor-or-self::*[@id = $detachable_elements/@id]""";
| "«$pagename»": {
- //| widget: hmi_widgets["«@id»"],
| bbox: [«$p/@x», «$p/@y», «$p/@w», «$p/@h»],
- if "$desc/path/@value" {
- if "count($desc/path/@index)=0"
- warning > Page id="«$page/@id»" : No match for path "«$desc/path/@value»" in HMI tree
- | page_index: «$desc/path/@index»,
- | page_class: "«$indexed_hmitree/*[@hmipath = $desc/path/@value]/@class»",
+ if "count($page_root_path)=1"{
+ if "count($page_root_path/@index)=0"
+ warning > Page id="«$page/@id»" : No match for path "«$page_root_path/@value»" in HMI tree
+ | page_index: «$page_root_path/@index»,
+ | page_class: "«$indexed_hmitree/*[@hmipath = $page_root_path/@value]/@class»",
}
| widgets: [
+ | [hmi_widgets["«$page/@id»"], []],
foreach "$page_managed_widgets" {
const "widget_paths_relativeness"
foreach "func:widget(@id)/path" {
- value "func:is_descendant_path(@value, $desc/path/@value)";
+ value "func:is_descendant_path(@value, $page_root_path/@value)";
if "position()!=last()" > ,
}
| [hmi_widgets["«@id»"], [«$widget_paths_relativeness»]]`if "position()!=last()" > ,`
diff -r ed1ec3136c2b -r 0c06de0a39b5 svghmi/gen_index_xhtml.xslt
--- a/svghmi/gen_index_xhtml.xslt Mon Feb 27 13:47:36 2023 +0100
+++ b/svghmi/gen_index_xhtml.xslt Tue Feb 28 09:39:51 2023 +0100
@@ -734,6 +734,21 @@
+
+
+
+
+ /*
+
+ */
+
+
+
+ class PageWidget extends Widget{}
+
+
+
+
@@ -787,7 +802,15 @@
-
+
+
+
+ Page id="
+
+ " : only one root path can be declared
+
+
+
"
@@ -804,31 +827,35 @@
],
-
-
+
+
Page id="
" : No match for path "
-
+
" in HMI tree
page_index:
-
+
,
page_class: "
-
+
",
widgets: [
+ [hmi_widgets["
+
+ "], []],
+
-
+
,
@@ -1190,6 +1217,8 @@
+ const xmlns = "http://www.w3.org/2000/svg";
+
let id = document.getElementById.bind(document);
var svg_root = id("
@@ -1440,7 +1469,7 @@
,{
- assignments: [],
+ enable_assignments: [],
compute_enable: function(value, oldval, varnum) {
@@ -1456,13 +1485,13 @@
if(varnum ==
- ) this.assignments[
+ ) this.enable_assignments[
] = value;
let
- = this.assignments[
+ = this.enable_assignments[
];
@@ -2131,7 +2160,7 @@
}
-
+
undeafen(index){
@@ -2141,6 +2170,10 @@
this.incoming[index] = undefined;
+ // TODO: add timestamp argument to dispatch, so that defered data do not appear wrong on graphs
+
+ this.lastdispatch[index] = Date.now();
+
this.do_dispatch(new_val, old_val, index);
}
@@ -2388,7 +2421,9 @@
+
+
@@ -2402,7 +2437,7 @@
var hmi_widgets = {
-
+
}
@@ -5557,7 +5592,7 @@
animate: function(){
- this.value_elt.textContent = String(this.display);
+ multiline_to_svg_text(this.value_elt, String(this.display));
},
@@ -5595,7 +5630,7 @@
- this.value_elt.textContent = "";
+ multiline_to_svg_text(this.value_elt, "");
},
@@ -6367,10 +6402,10 @@
-
+
-
+
@@ -7195,6 +7230,128 @@
],
+
+
+
+
+
+
+
+ Arguments are either:
+
+
+
+ - XXX reference path TODO
+
+
+
+ - name=value: setting variable with literal value.
+
+ - name=other_name: copy variable content into another
+
+
+
+ "active"+"inactive" labeled elements can be provided to show feedback when pressed
+
+
+
+ Exemples:
+
+
+
+ HMI:Page:notify=1@notify=/PLCVAR
+
+ HMI:Page:ack=2:notify=1@ack=.local_var@notify=/PLCVAR
+
+
+
+
+
+ Page
+
+
+
+
+
+
+
+
+ /disabled
+
+
+
+
+
+
+ assignments: {},
+
+ dispatch: function(value, oldval, varnum) {
+
+
+
+
+
+
+
+
+ if(varnum ==
+
+ ) this.assignments["
+
+ "] = value;
+
+
+
+
+
+ },
+
+ assign: function() {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ const
+
+ = this.assignments["
+
+ "];
+
+ if(
+
+ != undefined)
+
+ this.apply_hmi_value(
+
+ ,
+
+ );
+
+
+
+ this.apply_hmi_value(
+
+ ,
+
+ );
+
+
+
+
+ },
+
+
@@ -8994,21 +9151,23 @@
// Compute visible Y range by merging fixed curves Y ranges
- for(let minmax of this.minmaxes){
-
- if(minmax){
-
- let [min,max] = minmax;
-
- if(min < y_min)
-
- y_min = min;
-
- if(max > y_max)
-
- y_max = max;
-
- }
+ for(let varopts of this.variables_options){
+
+ let minmax = varopts.minmax
+
+ if(minmax){
+
+ let [min,max] = minmax;
+
+ if(min < y_min)
+
+ y_min = min;
+
+ if(max > y_max)
+
+ y_max = max;
+
+ }
}
@@ -9016,11 +9175,11 @@
if(y_min !== Infinity && y_max !== -Infinity){
- this.fixed_y_range = true;
+ this.fixed_y_range = true;
} else {
- this.fixed_y_range = false;
+ this.fixed_y_range = false;
}
@@ -9114,6 +9273,8 @@
+ console.log("dispatch(",value,oldval, index, time);
+
// naive local buffer impl.
// data is updated only when graph is visible
@@ -12128,29 +12289,37 @@
- var screensaver_timer = null;
-
- function reset_screensaver_timer() {
-
- if(screensaver_timer){
-
- window.clearTimeout(screensaver_timer);
+ if(screensaver_delay){
+
+ var screensaver_timer = null;
+
+ function reset_screensaver_timer() {
+
+ if(screensaver_timer){
+
+ window.clearTimeout(screensaver_timer);
+
+ }
+
+ screensaver_timer = window.setTimeout(() => {
+
+ switch_page("ScreenSaver");
+
+ screensaver_timer = null;
+
+ }, screensaver_delay*1000);
}
- screensaver_timer = window.setTimeout(() => {
-
- switch_page("ScreenSaver");
-
- screensaver_timer = null;
-
- }, screensaver_delay*1000);
+ document.body.addEventListener('pointerdown', reset_screensaver_timer);
+
+ // initialize screensaver
+
+ reset_screensaver_timer();
}
- if(screensaver_delay)
-
- document.body.addEventListener('pointerdown', reset_screensaver_timer);
+
@@ -12314,6 +12483,12 @@
+ // when entering a page, assignments are evaluated
+
+ new_desc.widgets[0][0].assign();
+
+
+
return true;
};
@@ -12474,16 +12649,12 @@
- // initialize screensaver
-
- reset_screensaver_timer();
-
-
-
var reconnect_delay = 0;
var periodic_reconnect_timer;
+ var force_reconnect = false;
+
// Once connection established
@@ -12504,6 +12675,8 @@
periodic_reconnect_timer = window.setTimeout(() => {
+ force_reconnect = true;
+
ws.close();
periodic_reconnect_timer = null;
@@ -12540,14 +12713,26 @@
ws = null;
- // reconect
-
- // TODO : add visible notification while waiting for reload
+ // Do not attempt to reconnect immediately in case:
+
+ // - connection was closed by server (PLC stop)
+
+ // - connection was closed locally with an intention to reconnect
+
+ if(evt.code=1000 && !force_reconnect){
+
+ window.alert("Connection closed by server");
+
+ location.reload();
+
+ }
window.setTimeout(create_ws, reconnect_delay);
reconnect_delay += 500;
+ force_reconnect = false;
+
};
@@ -12580,8 +12765,6 @@
- const xmlns = "http://www.w3.org/2000/svg";
-
var edit_callback;
const localtypes = {"PAGE_LOCAL":null, "HMI_LOCAL":null}
diff -r ed1ec3136c2b -r 0c06de0a39b5 svghmi/inline_svg.ysl2
--- a/svghmi/inline_svg.ysl2 Mon Feb 27 13:47:36 2023 +0100
+++ b/svghmi/inline_svg.ysl2 Tue Feb 28 09:39:51 2023 +0100
@@ -209,6 +209,7 @@
const "result_svg_ns", "exsl:node-set($result_svg)";
emit "preamble:inline-svg" {
+ | const xmlns = "http://www.w3.org/2000/svg";
| let id = document.getElementById.bind(document);
| var svg_root = id("«$svg/@id»");
}
diff -r ed1ec3136c2b -r 0c06de0a39b5 svghmi/svghmi.js
--- a/svghmi/svghmi.js Mon Feb 27 13:47:36 2023 +0100
+++ b/svghmi/svghmi.js Tue Feb 28 09:39:51 2023 +0100
@@ -525,6 +525,9 @@
? page_name
: page_name + "@" + hmitree_paths[page_index]);
+ // when entering a page, assignments are evaluated
+ new_desc.widgets[0][0].assign();
+
return true;
};
@@ -607,6 +610,7 @@
var reconnect_delay = 0;
var periodic_reconnect_timer;
+var force_reconnect = false;
// Once connection established
function ws_onopen(evt) {
@@ -617,6 +621,7 @@
window.clearTimeout(periodic_reconnect_timer);
}
periodic_reconnect_timer = window.setTimeout(() => {
+ force_reconnect = true;
ws.close();
periodic_reconnect_timer = null;
}, 3600000);
@@ -635,10 +640,16 @@
function ws_onclose(evt) {
console.log("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+" Reload in "+reconnect_delay+"ms.");
ws = null;
- // reconect
- // TODO : add visible notification while waiting for reload
+ // Do not attempt to reconnect immediately in case:
+ // - connection was closed by server (PLC stop)
+ // - connection was closed locally with an intention to reconnect
+ if(evt.code=1000 && !force_reconnect){
+ window.alert("Connection closed by server");
+ location.reload();
+ }
window.setTimeout(create_ws, reconnect_delay);
reconnect_delay += 500;
+ force_reconnect = false;
};
var ws_url =
@@ -655,7 +666,6 @@
create_ws()
-const xmlns = "http://www.w3.org/2000/svg";
var edit_callback;
const localtypes = {"PAGE_LOCAL":null, "HMI_LOCAL":null}
function edit_value(path, valuetype, callback, initial) {
diff -r ed1ec3136c2b -r 0c06de0a39b5 svghmi/svghmi.py
--- a/svghmi/svghmi.py Mon Feb 27 13:47:36 2023 +0100
+++ b/svghmi/svghmi.py Tue Feb 28 09:39:51 2023 +0100
@@ -650,8 +650,8 @@
def svghmi_{location}_watchdog_trigger():
global browser_proc
- restart_proc = {svghmi_cmds[Watchdog]}
- waitpid_timeout(restart_proc, "SVGHMI watchdog triggered command")
+ watchdog_proc = {svghmi_cmds[Watchdog]}
+ waitpid_timeout(watchdog_proc, "SVGHMI watchdog triggered command")
stop_proc = {svghmi_cmds[Stop]}
waitpid_timeout(stop_proc, "SVGHMI stop command")
waitpid_timeout(browser_proc, "SVGHMI browser process")
diff -r ed1ec3136c2b -r 0c06de0a39b5 svghmi/widget_input.ysl2
--- a/svghmi/widget_input.ysl2 Mon Feb 27 13:47:36 2023 +0100
+++ b/svghmi/widget_input.ysl2 Tue Feb 28 09:39:51 2023 +0100
@@ -92,7 +92,7 @@
if "$have_value" {
| animate: function(){
- | this.value_elt.textContent = String(this.display);
+ | multiline_to_svg_text(this.value_elt, String(this.display));
| },
}
@@ -114,7 +114,7 @@
}
if "$have_value" {
- | this.value_elt.textContent = "";
+ | multiline_to_svg_text(this.value_elt, "");
}
| },
}
diff -r ed1ec3136c2b -r 0c06de0a39b5 svghmi/widget_jump.ysl2
--- a/svghmi/widget_jump.ysl2 Mon Feb 27 13:47:36 2023 +0100
+++ b/svghmi/widget_jump.ysl2 Tue Feb 28 09:39:51 2023 +0100
@@ -143,8 +143,8 @@
otherwise value "$page_desc/arg[1]/@value";
}
const "target_page_path" choose {
- when "arg" value "$hmi_pages_descs[arg[1]/@value = $target_page_name]/path[1]/@value";
- otherwise value "$page_desc/path[1]/@value";
+ when "arg" value "$hmi_pages_descs[arg[1]/@value = $target_page_name]/path[not(@assign)]/@value";
+ otherwise value "$page_desc/path[not(@assign)]/@value";
}
if "not(func:same_class_paths($target_page_path, path[1]/@value))"
diff -r ed1ec3136c2b -r 0c06de0a39b5 svghmi/widget_page.ysl2
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/svghmi/widget_page.ysl2 Tue Feb 28 09:39:51 2023 +0100
@@ -0,0 +1,60 @@
+// widget_page.ysl2
+
+widget_desc("Page") {
+ longdesc
+ ||
+
+ Arguments are either:
+
+ - XXX reference path TODO
+
+ - name=value: setting variable with literal value.
+ - name=other_name: copy variable content into another
+
+ "active"+"inactive" labeled elements can be provided to show feedback when pressed
+
+ Exemples:
+
+ HMI:Page:notify=1@notify=/PLCVAR
+ HMI:Page:ack=2:notify=1@ack=.local_var@notify=/PLCVAR
+
+ ||
+
+ shortdesc > Page
+
+}
+
+widget_defs("Page") {
+
+ | assignments: {},
+ | dispatch: function(value, oldval, varnum) {
+ const "widget", ".";
+ foreach "path" {
+ const "varid","generate-id()";
+ const "varnum","position()-1";
+ if "@assign" foreach "$widget/path[@assign]" if "$varid = generate-id()" {
+ | if(varnum == «$varnum») this.assignments["«@assign»"] = value;
+ }
+ }
+ | },
+ | assign: function() {
+ const "paths","path";
+ foreach "arg[contains(@value,'=')]"{
+ const "name","substring-before(@value,'=')";
+ const "value","substring-after(@value,'=')";
+ const "index" foreach "$paths" if "@assign = $name" value "position()-1";
+ const "isVarName", "regexp:test($value,'^[a-zA-Z_][a-zA-Z0-9_]+$')";
+ choose {
+ when "$isVarName"{
+ | const «$value» = this.assignments["«$value»"];
+ | if(«$value» != undefined)
+ | this.apply_hmi_value(«$index», «$value»);
+ }
+ otherwise {
+ | this.apply_hmi_value(«$index», «$value»);
+ }
+ }
+ }
+ | },
+}
+
diff -r ed1ec3136c2b -r 0c06de0a39b5 svghmi/widget_xygraph.ysl2
--- a/svghmi/widget_xygraph.ysl2 Mon Feb 27 13:47:36 2023 +0100
+++ b/svghmi/widget_xygraph.ysl2 Tue Feb 28 09:39:51 2023 +0100
@@ -58,20 +58,21 @@
let y_min = Infinity, y_max = -Infinity;
// Compute visible Y range by merging fixed curves Y ranges
- for(let minmax of this.minmaxes){
- if(minmax){
- let [min,max] = minmax;
- if(min < y_min)
- y_min = min;
- if(max > y_max)
- y_max = max;
- }
+ for(let varopts of this.variables_options){
+ let minmax = varopts.minmax
+ if(minmax){
+ let [min,max] = minmax;
+ if(min < y_min)
+ y_min = min;
+ if(max > y_max)
+ y_max = max;
+ }
}
if(y_min !== Infinity && y_max !== -Infinity){
- this.fixed_y_range = true;
+ this.fixed_y_range = true;
} else {
- this.fixed_y_range = false;
+ this.fixed_y_range = false;
}
this.ymin = y_min;
diff -r ed1ec3136c2b -r 0c06de0a39b5 svghmi/widgetlib/bool_indicator.svg
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/svghmi/widgetlib/bool_indicator.svg Tue Feb 28 09:39:51 2023 +0100
@@ -0,0 +1,82 @@
+
+
+
+
diff -r ed1ec3136c2b -r 0c06de0a39b5 svghmi/widgetlib/simple_text_display.svg
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/svghmi/widgetlib/simple_text_display.svg Tue Feb 28 09:39:51 2023 +0100
@@ -0,0 +1,64 @@
+
+
+
+
diff -r ed1ec3136c2b -r 0c06de0a39b5 svghmi/widgets_common.ysl2
--- a/svghmi/widgets_common.ysl2 Mon Feb 27 13:47:36 2023 +0100
+++ b/svghmi/widgets_common.ysl2 Tue Feb 28 09:39:51 2023 +0100
@@ -134,7 +134,7 @@
| "«@id»": new «$widget/@type»Widget ("«@id»",«$freq»,[«$args»],[«$variables»],«$enable_expr»,{
if "$widget/@enable_expr" {
- | assignments: [],
+ | enable_assignments: [],
| compute_enable: function(value, oldval, varnum) {
| let result = false;
| do {
@@ -142,8 +142,8 @@
const "varid","generate-id()";
const "varnum","position()-1";
if "@assign" foreach "$widget/path[@assign]" if "$varid = generate-id()" {
- | if(varnum == «$varnum») this.assignments[«position()-1»] = value;
- | let «@assign» = this.assignments[«position()-1»];
+ | if(varnum == «$varnum») this.enable_assignments[«position()-1»] = value;
+ | let «@assign» = this.enable_assignments[«position()-1»];
| if(«@assign» == undefined) break;
}
}
@@ -464,11 +464,12 @@
}
}
}
-
+
undeafen(index){
this.deafen[index] = undefined;
let [new_val, old_val] = this.incoming[index];
this.incoming[index] = undefined;
+ this.lastdispatch[index] = Date.now();
this.do_dispatch(new_val, old_val, index);
}
@@ -600,12 +601,14 @@
}
const "included_ids","$parsed_widgets/widget[not(@type = $excluded_types) and not(@id = $discardable_elements/@id)]/@id";
+const "page_ids","$parsed_widgets/widget[@type = 'Page']/@id";
const "hmi_widgets","$hmi_elements[@id = $included_ids]";
+const "page_widgets","$hmi_elements[@id = $page_ids]";
const "result_widgets","$result_svg_ns//*[@id = $hmi_widgets/@id]";
emit "declarations:hmi-elements" {
| var hmi_widgets = {
- apply "$hmi_widgets", mode="hmi_widgets";
+ apply "$hmi_widgets | $page_widgets", mode="hmi_widgets";
| }
|
}
diff -r ed1ec3136c2b -r 0c06de0a39b5 targets/Linux/XSD
--- a/targets/Linux/XSD Mon Feb 27 13:47:36 2023 +0100
+++ b/targets/Linux/XSD Tue Feb 28 09:39:51 2023 +0100
@@ -1,6 +1,7 @@
+
%(toolchain_gcc)s
-
\ No newline at end of file
+
diff -r ed1ec3136c2b -r 0c06de0a39b5 targets/Linux/__init__.py
--- a/targets/Linux/__init__.py Mon Feb 27 13:47:36 2023 +0100
+++ b/targets/Linux/__init__.py Tue Feb 28 09:39:51 2023 +0100
@@ -32,7 +32,11 @@
extension = ".so"
def getBuilderCFLAGS(self):
- return toolchain_gcc.getBuilderCFLAGS(self) + ["-fPIC"]
+ additional_cflags = ["-fPIC"]
+ build_for_realtime = self.CTRInstance.GetTarget().getcontent().getRealTime()
+ if build_for_realtime:
+ additional_cflags.append("-DREALTIME_LINUX")
+ return toolchain_gcc.getBuilderCFLAGS(self) + additional_cflags
def getBuilderLDFLAGS(self):
return toolchain_gcc.getBuilderLDFLAGS(self) + ["-shared", "-lrt"]
diff -r ed1ec3136c2b -r 0c06de0a39b5 targets/Linux/plc_Linux_main.c
--- a/targets/Linux/plc_Linux_main.c Mon Feb 27 13:47:36 2023 +0100
+++ b/targets/Linux/plc_Linux_main.c Tue Feb 28 09:39:51 2023 +0100
@@ -7,11 +7,23 @@
#include
#include
#include
+#include
#include
#include
#include
-
-static sem_t Run_PLC;
+#ifdef REALTIME_LINUX
+#include
+#endif
+
+static unsigned long __debug_tick;
+
+static pthread_t PLC_thread;
+static pthread_mutex_t python_wait_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t python_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t debug_wait_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t debug_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static int PLC_shutdown = 0;
long AtomicCompareExchange(long* atomicvar,long compared, long exchange)
{
@@ -30,39 +42,34 @@
CURRENT_TIME->tv_nsec = tmp.tv_nsec;
}
-void PLC_timer_notify(sigval_t val)
-{
- PLC_GetTime(&__CURRENT_TIME);
- sem_post(&Run_PLC);
-}
-
-timer_t PLC_timer;
+static long long period_ns = 0;
+struct timespec next_abs_time;
+
+static void inc_timespec(struct timespec *ts, unsigned long long value_ns)
+{
+ long long next_ns = ((long long) ts->tv_sec * 1000000000) + ts->tv_nsec + value_ns;
+#ifdef __lldiv_t_defined
+ lldiv_t next_div = lldiv(next_ns, 1000000000);
+ ts->tv_sec = next_div.quot;
+ ts->tv_nsec = next_div.rem;
+#else
+ ts->tv_sec = next_ns / 1000000000;
+ ts->tv_nsec = next_ns % 1000000000;
+#endif
+}
void PLC_SetTimer(unsigned long long next, unsigned long long period)
{
- struct itimerspec timerValues;
- /*
- printf("SetTimer(%lld,%lld)\n",next, period);
- */
- memset (&timerValues, 0, sizeof (struct itimerspec));
- {
-#ifdef __lldiv_t_defined
- lldiv_t nxt_div = lldiv(next, 1000000000);
- lldiv_t period_div = lldiv(period, 1000000000);
- timerValues.it_value.tv_sec = nxt_div.quot;
- timerValues.it_value.tv_nsec = nxt_div.rem;
- timerValues.it_interval.tv_sec = period_div.quot;
- timerValues.it_interval.tv_nsec = period_div.rem;
-#else
- timerValues.it_value.tv_sec = next / 1000000000;
- timerValues.it_value.tv_nsec = next % 1000000000;
- timerValues.it_interval.tv_sec = period / 1000000000;
- timerValues.it_interval.tv_nsec = period % 1000000000;
-#endif
- }
- timer_settime (PLC_timer, 0, &timerValues, NULL);
-}
-//
+ /*
+ printf("SetTimer(%lld,%lld)\n",next, period);
+ */
+ period_ns = period;
+ clock_gettime(CLOCK_MONOTONIC, &next_abs_time);
+ inc_timespec(&next_abs_time, next);
+ // interrupt clock_nanpsleep
+ pthread_kill(PLC_thread, SIGUSR1);
+}
+
void catch_signal(int sig)
{
// signal(SIGTERM, catch_signal);
@@ -71,16 +78,11 @@
exit(0);
}
-
-static unsigned long __debug_tick;
-
-pthread_t PLC_thread;
-static pthread_mutex_t python_wait_mutex = PTHREAD_MUTEX_INITIALIZER;
-static pthread_mutex_t python_mutex = PTHREAD_MUTEX_INITIALIZER;
-static pthread_mutex_t debug_wait_mutex = PTHREAD_MUTEX_INITIALIZER;
-static pthread_mutex_t debug_mutex = PTHREAD_MUTEX_INITIALIZER;
-
-int PLC_shutdown = 0;
+void PLCThreadSignalHandler(int sig)
+{
+ if (sig == SIGUSR2)
+ pthread_exit(NULL);
+}
int ForceSaveRetainReq(void) {
return PLC_shutdown;
@@ -89,30 +91,79 @@
void PLC_thread_proc(void *arg)
{
while (!PLC_shutdown) {
- sem_wait(&Run_PLC);
+ // Sleep until next PLC run
+ // TODO check result of clock_nanosleep and wait again or exit eventually
+ int res = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next_abs_time, NULL);
+ if(res==EINTR){
+ continue;
+ }
+ if(res!=0){
+ printf("PLC thread died with error %d \n", res);
+ return;
+ }
+ PLC_GetTime(&__CURRENT_TIME);
__run();
+ inc_timespec(&next_abs_time, period_ns);
}
pthread_exit(0);
}
+#define _LogError(text,...) \
+ {\
+ char mstr[256];\
+ snprintf(mstr, 255, text, ##__VA_ARGS__);\
+ LogMessage(LOG_CRITICAL, mstr, strlen(mstr));\
+ }
#define maxval(a,b) ((a>b)?a:b)
int startPLC(int argc,char **argv)
{
- struct sigevent sigev;
- setlocale(LC_NUMERIC, "C");
+
+ int ret;
+ pthread_attr_t *pattr = NULL;
+
+#ifdef REALTIME_LINUX
+ struct sched_param param;
+ pthread_attr_t attr;
+
+ /* Lock memory */
+ ret = mlockall(MCL_CURRENT|MCL_FUTURE);
+ if(ret == -1) {
+ _LogError("mlockall failed: %m\n");
+ return ret;
+ }
+
+ /* Initialize pthread attributes (default values) */
+ ret = pthread_attr_init(&attr);
+ if (ret) {
+ _LogError("init pthread attributes failed\n");
+ return ret;
+ }
+
+ /* Set scheduler policy and priority of pthread */
+ ret = pthread_attr_setschedpolicy(&attr, SCHED_FIFO);
+ if (ret) {
+ _LogError("pthread setschedpolicy failed\n");
+ return ret;
+ }
+ param.sched_priority = PLC_THREAD_PRIORITY;
+ ret = pthread_attr_setschedparam(&attr, ¶m);
+ if (ret) {
+ _LogError("pthread setschedparam failed\n");
+ return ret;
+ }
+
+ /* Use scheduling parameters of attr */
+ ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
+ if (ret) {
+ _LogError("pthread setinheritsched failed\n");
+ return ret;
+ }
+
+ pattr = &attr;
+#endif
PLC_shutdown = 0;
- sem_init(&Run_PLC, 0, 0);
-
- pthread_create(&PLC_thread, NULL, (void*) &PLC_thread_proc, NULL);
-
- memset (&sigev, 0, sizeof (struct sigevent));
- sigev.sigev_value.sival_int = 0;
- sigev.sigev_notify = SIGEV_THREAD;
- sigev.sigev_notify_attributes = NULL;
- sigev.sigev_notify_function = PLC_timer_notify;
-
pthread_mutex_init(&debug_wait_mutex, NULL);
pthread_mutex_init(&debug_mutex, NULL);
pthread_mutex_init(&python_wait_mutex, NULL);
@@ -121,14 +172,26 @@
pthread_mutex_lock(&debug_wait_mutex);
pthread_mutex_lock(&python_wait_mutex);
- timer_create (CLOCK_MONOTONIC, &sigev, &PLC_timer);
- if( __init(argc,argv) == 0 ){
- PLC_SetTimer(common_ticktime__,common_ticktime__);
-
+ if((ret = __init(argc,argv)) == 0 ){
+
+ /* Signal to wakeup PLC thread when period changes */
+ signal(SIGUSR1, PLCThreadSignalHandler);
+ /* Signal to end PLC thread */
+ signal(SIGUSR2, PLCThreadSignalHandler);
/* install signal handler for manual break */
signal(SIGINT, catch_signal);
+
+ /* initialize next occurence and period */
+ period_ns = common_ticktime__;
+ clock_gettime(CLOCK_MONOTONIC, &next_abs_time);
+
+ ret = pthread_create(&PLC_thread, pattr, (void*) &PLC_thread_proc, NULL);
+ if (ret) {
+ _LogError("create pthread failed\n");
+ return ret;
+ }
}else{
- return 1;
+ return ret;
}
return 0;
}
@@ -154,11 +217,9 @@
{
/* Stop the PLC */
PLC_shutdown = 1;
- sem_post(&Run_PLC);
- PLC_SetTimer(0,0);
- pthread_join(PLC_thread, NULL);
- sem_destroy(&Run_PLC);
- timer_delete (PLC_timer);
+ /* Order PLCThread to exit */
+ pthread_kill(PLC_thread, SIGUSR2);
+ pthread_join(PLC_thread, NULL);
__cleanup();
pthread_mutex_destroy(&debug_wait_mutex);
pthread_mutex_destroy(&debug_mutex);
@@ -196,7 +257,7 @@
/*__DEBUG is protected by this mutex */
__DEBUG = !disable;
if (disable)
- pthread_mutex_unlock(&debug_mutex);
+ pthread_mutex_unlock(&debug_mutex);
return 0;
}
@@ -237,6 +298,7 @@
}
struct RT_to_nRT_signal_s {
+ int used;
pthread_cond_t WakeCond;
pthread_mutex_t WakeCondLock;
};
@@ -245,7 +307,7 @@
#define _LogAndReturnNull(text) \
{\
- char mstr[256] = text " for ";\
+ char mstr[256] = text " for ";\
strncat(mstr, name, 255);\
LogMessage(LOG_CRITICAL, mstr, strlen(mstr));\
return NULL;\
@@ -254,9 +316,10 @@
void *create_RT_to_nRT_signal(char* name){
RT_to_nRT_signal_t *sig = (RT_to_nRT_signal_t*)malloc(sizeof(RT_to_nRT_signal_t));
- if(!sig)
- _LogAndReturnNull("Failed allocating memory for RT_to_nRT signal");
-
+ if(!sig)
+ _LogAndReturnNull("Failed allocating memory for RT_to_nRT signal");
+
+ sig->used = 1;
pthread_cond_init(&sig->WakeCond, NULL);
pthread_mutex_init(&sig->WakeCondLock, NULL);
@@ -266,10 +329,10 @@
void delete_RT_to_nRT_signal(void* handle){
RT_to_nRT_signal_t *sig = (RT_to_nRT_signal_t*)handle;
- pthread_cond_destroy(&sig->WakeCond);
- pthread_mutex_destroy(&sig->WakeCondLock);
-
- free(sig);
+ pthread_mutex_lock(&sig->WakeCondLock);
+ sig->used = 0;
+ pthread_cond_signal(&sig->WakeCond);
+ pthread_mutex_unlock(&sig->WakeCondLock);
}
int wait_RT_to_nRT_signal(void* handle){
@@ -277,7 +340,14 @@
RT_to_nRT_signal_t *sig = (RT_to_nRT_signal_t*)handle;
pthread_mutex_lock(&sig->WakeCondLock);
ret = pthread_cond_wait(&sig->WakeCond, &sig->WakeCondLock);
+ if(!sig->used) ret = -EINVAL;
pthread_mutex_unlock(&sig->WakeCondLock);
+
+ if(!sig->used){
+ pthread_cond_destroy(&sig->WakeCond);
+ pthread_mutex_destroy(&sig->WakeCondLock);
+ free(sig);
+ }
return ret;
}
diff -r ed1ec3136c2b -r 0c06de0a39b5 targets/Win32/plc_Win32_main.c
--- a/targets/Win32/plc_Win32_main.c Mon Feb 27 13:47:36 2023 +0100
+++ b/targets/Win32/plc_Win32_main.c Tue Feb 28 09:39:51 2023 +0100
@@ -82,7 +82,6 @@
{
unsigned long thread_id = 0;
BOOL tmp;
- setlocale(LC_NUMERIC, "C");
debug_sem = CreateSemaphore(
NULL, // default security attributes
diff -r ed1ec3136c2b -r 0c06de0a39b5 targets/beremiz.h
--- a/targets/beremiz.h Mon Feb 27 13:47:36 2023 +0100
+++ b/targets/beremiz.h Tue Feb 28 09:39:51 2023 +0100
@@ -32,4 +32,13 @@
int unblock_RT_to_nRT_signal(void* handle);
void nRT_reschedule(void);
+
+#ifdef REALTIME_LINUX
+
+#ifndef PLC_THREAD_PRIORITY
+#define PLC_THREAD_PRIORITY 80
#endif
+
+#endif
+
+#endif
diff -r ed1ec3136c2b -r 0c06de0a39b5 targets/plc_debug.c
--- a/targets/plc_debug.c Mon Feb 27 13:47:36 2023 +0100
+++ b/targets/plc_debug.c Tue Feb 28 09:39:51 2023 +0100
@@ -270,7 +270,7 @@
default:
break;
}
- force_list_apply_cursor++; \
+ force_list_apply_cursor++;
}
/* Reset buffer cursor */
diff -r ed1ec3136c2b -r 0c06de0a39b5 tests/projects/svghmi/svghmi_0@svghmi/svghmi.svg
--- a/tests/projects/svghmi/svghmi_0@svghmi/svghmi.svg Mon Feb 27 13:47:36 2023 +0100
+++ b/tests/projects/svghmi/svghmi_0@svghmi/svghmi.svg Tue Feb 28 09:39:51 2023 +0100
@@ -136,11 +136,11 @@
inkscape:current-layer="hmi0"
showgrid="false"
units="px"
- inkscape:zoom="0.40092403"
- inkscape:cx="2333.0807"
- inkscape:cy="1015.6842"
- inkscape:window-width="1600"
- inkscape:window-height="836"
+ inkscape:zoom="2.2679688"
+ inkscape:cx="1135.0439"
+ inkscape:cy="130.37028"
+ inkscape:window-width="1850"
+ inkscape:window-height="1036"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1"
@@ -149,6 +149,33 @@
inkscape:snap-global="true"
inkscape:snap-bbox="true"
inkscape:bbox-nodes="true" />
+
+
+ declaration of user_level HMI local variable(not a PLC variable)
+
+ user
Home
-
+ user
+ user
+
+
+ login
+