--- a/BeremizIDE.py Thu Jan 28 14:51:16 2021 +0000
+++ b/BeremizIDE.py Tue Feb 02 13:47:48 2021 +0100
@@ -103,7 +103,7 @@
}
else:
faces = {
- 'mono': 'Courier',
+ 'mono': 'FreeMono',
'size': 10,
}
--- a/CodeFileTreeNode.py Thu Jan 28 14:51:16 2021 +0000
+++ b/CodeFileTreeNode.py Tue Feb 02 13:47:48 2021 +0100
@@ -207,9 +207,6 @@
variable.gettype(),
variable.getinitial())
for variable in variables]
- ret.extend([("On"+variable.getname()+"Change", "python_poll", "")
- for variable in variables
- if variable.getonchange()])
return ret
def CTNSearch(self, criteria):
--- a/ConfigTreeNode.py Thu Jan 28 14:51:16 2021 +0000
+++ b/ConfigTreeNode.py Tue Feb 02 13:47:48 2021 +0100
@@ -46,6 +46,7 @@
from xmlclass import GenerateParserFromXSDstring
from PLCControler import LOCATION_CONFNODE
from editors.ConfTreeNodeEditor import ConfTreeNodeEditor
+from POULibrary import UserAddressedException
_BaseParamsParser = GenerateParserFromXSDstring("""<?xml version="1.0" encoding="ISO-8859-1" ?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
@@ -469,21 +470,21 @@
def GetContextualMenuItems(self):
return None
- def GetView(self):
- if self._View is None and self.EditorType is not None:
+ def GetView(self, onlyopened=False):
+ if self._View is None and not onlyopened and self.EditorType is not None:
app_frame = self.GetCTRoot().AppFrame
self._View = self.EditorType(app_frame.TabsOpened, self, app_frame)
return self._View
def _OpenView(self, name=None, onlyopened=False):
- view = self.GetView()
+ view = self.GetView(onlyopened)
if view is not None:
if name is None:
name = self.CTNFullName()
app_frame = self.GetCTRoot().AppFrame
- app_frame.EditProjectElement(view, name)
+ app_frame.EditProjectElement(view, name, onlyopened)
return view
--- a/ProjectController.py Thu Jan 28 14:51:16 2021 +0000
+++ b/ProjectController.py Tue Feb 02 13:47:48 2021 +0100
@@ -159,6 +159,7 @@
no_stdout=True,
no_stderr=True).spin()
except Exception:
+ self.logger.write_error(_("Couldn't launch IEC compiler to determine compatible options.\n"))
return buildopt
for opt in options:
@@ -254,7 +255,7 @@
# Setup debug information
self.IECdebug_datas = {}
- self.DebugTimer = None
+ self.DebugUpdatePending = False
self.ResetIECProgramsAndVariables()
# In both new or load scenario, no need to save
@@ -276,8 +277,6 @@
self.debug_status = PlcStatus.Stopped
def __del__(self):
- if self.DebugTimer:
- self.DebugTimer.cancel()
self.KillDebugThread()
def LoadLibraries(self):
@@ -1538,7 +1537,6 @@
return debug_status, ticks, buffers
def RegisterDebugVarToConnector(self):
- self.DebugTimer = None
Idxs = []
self.TracedIECPath = []
self.TracedIECTypes = []
@@ -1577,25 +1575,15 @@
self._connector.SetTraceVariablesList([])
self.DebugToken = None
self.debug_status, _debug_ticks, _buffers = self.SnapshotAndResetDebugValuesBuffers()
+ self.DebugUpdatePending = False
def IsPLCStarted(self):
return self.previous_plcstate == PlcStatus.Started
- def ReArmDebugRegisterTimer(self):
- if self.DebugTimer is not None:
- self.DebugTimer.cancel()
-
- # Prevent to call RegisterDebugVarToConnector when PLC is not started
- # If an output location var is forced it's leads to segmentation fault in runtime
- # Links between PLC located variables and real variables are not ready
- if self.IsPLCStarted():
- # Timer to prevent rapid-fire when registering many variables
- # use wx.CallAfter use keep using same thread. TODO : use wx.Timer
- # instead
- self.DebugTimer = Timer(
- 0.5, wx.CallAfter, args=[self.RegisterDebugVarToConnector])
- # Rearm anti-rapid-fire timer
- self.DebugTimer.start()
+ def AppendDebugUpdate(self):
+ if not self.DebugUpdatePending :
+ wx.CallAfter(self.RegisterDebugVarToConnector)
+ self.DebugUpdatePending = True
def GetDebugIECVariableType(self, IECPath):
_Idx, IEC_Type = self._IECPathToIdx.get(IECPath, (None, None))
@@ -1625,7 +1613,7 @@
IECdebug_data[0][callableobj] = buffer_list
- self.ReArmDebugRegisterTimer()
+ self.AppendDebugUpdate()
return IECdebug_data[1]
@@ -1641,12 +1629,12 @@
IECdebug_data[0].itervalues(),
False)
- self.ReArmDebugRegisterTimer()
+ self.AppendDebugUpdate()
def UnsubscribeAllDebugIECVariable(self):
self.IECdebug_datas = {}
- self.ReArmDebugRegisterTimer()
+ self.AppendDebugUpdate()
def ForceDebugIECVariable(self, IECPath, fvalue):
if IECPath not in self.IECdebug_datas:
@@ -1657,7 +1645,7 @@
IECdebug_data[2] = "Forced"
IECdebug_data[3] = fvalue
- self.ReArmDebugRegisterTimer()
+ self.AppendDebugUpdate()
def ReleaseDebugIECVariable(self, IECPath):
if IECPath not in self.IECdebug_datas:
@@ -1668,7 +1656,7 @@
IECdebug_data[2] = "Registered"
IECdebug_data[3] = None
- self.ReArmDebugRegisterTimer()
+ self.AppendDebugUpdate()
def CallWeakcallables(self, IECPath, function_name, *cargs):
data_tuple = self.IECdebug_datas.get(IECPath, None)
--- a/XSLTransform.py Thu Jan 28 14:51:16 2021 +0000
+++ b/XSLTransform.py Tue Feb 02 13:47:48 2021 +0100
@@ -22,4 +22,7 @@
# print(self.xslt.error_log)
return res
+ def get_error_log(self):
+ return self.xslt.error_log
+
--- a/bacnet/bacnet.py Thu Jan 28 14:51:16 2021 +0000
+++ b/bacnet/bacnet.py Tue Feb 02 13:47:48 2021 +0100
@@ -790,6 +790,6 @@
runtimefile.close()
return ([(Generated_BACnet_c_mainfile_name, CFLAGS)], LDFLAGS, True,
- ("runtime_bacnet_websettings_%s.py" % location_str, open(runtimefile_path, "rb")),
+ ("runtime_%s_bacnet_websettings.py" % location_str, open(runtimefile_path, "rb")),
)
#return [(Generated_BACnet_c_mainfile_name, CFLAGS)], LDFLAGS, True, ('extrafile1.txt', extra_file_handle)
--- a/bacnet/web_settings.py Thu Jan 28 14:51:16 2021 +0000
+++ b/bacnet/web_settings.py Tue Feb 02 13:47:48 2021 +0100
@@ -295,7 +295,7 @@
# location_str is replaced by extension's value in CTNGenerateC call
-def _runtime_bacnet_websettings_%(location_str)s_init():
+def _runtime_%(location_str)s_bacnet_websettings_init():
"""
# Callback function, called (by PLCObject.py) when a new PLC program
# (i.e. XXX.so file) is transfered to the PLC runtime
@@ -383,7 +383,7 @@
# location_str is replaced by extension's value in CTNGenerateC call
-def _runtime_bacnet_websettings_%(location_str)s_cleanup():
+def _runtime_%(location_str)s_bacnet_websettings_cleanup():
"""
# Callback function, called (by PLCObject.py) when a PLC program is unloaded from memory
"""
--- a/controls/CustomStyledTextCtrl.py Thu Jan 28 14:51:16 2021 +0000
+++ b/controls/CustomStyledTextCtrl.py Tue Feb 02 13:47:48 2021 +0100
@@ -40,7 +40,7 @@
else:
faces = {
'times': 'Times',
- 'mono': 'Courier',
+ 'mono': 'FreeMono',
'helv': 'Helvetica',
'other': 'new century schoolbook',
'size': 12,
--- a/controls/LogViewer.py Thu Jan 28 14:51:16 2021 +0000
+++ b/controls/LogViewer.py Tue Feb 02 13:47:48 2021 +0100
@@ -339,7 +339,7 @@
if wx.Platform == '__WXMSW__':
self.Font = wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier New')
else:
- self.Font = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='Courier')
+ self.Font = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName='FreeMono')
self.MessagePanel.Bind(wx.EVT_LEFT_UP, self.OnMessagePanelLeftUp)
self.MessagePanel.Bind(wx.EVT_RIGHT_UP, self.OnMessagePanelRightUp)
self.MessagePanel.Bind(wx.EVT_LEFT_DCLICK, self.OnMessagePanelLeftDCLick)
--- a/docutil/docsvg.py Thu Jan 28 14:51:16 2021 +0000
+++ b/docutil/docsvg.py Tue Feb 02 13:47:48 2021 +0100
@@ -34,16 +34,23 @@
if wx.Platform == '__WXMSW__':
from six.moves import winreg
try:
- svgexepath = winreg.QueryValue(winreg.HKEY_LOCAL_MACHINE,
- 'Software\\Classes\\svgfile\\shell\\Inkscape\\command')
+ 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')
+ try:
+ svgexepath = winreg.QueryValue(winreg.HKEY_LOCAL_MACHINE,
+ 'Software\\Classes\\inkscape.svg\\shell\\open\\command')
+ except Exception:
+ return None
+
svgexepath = svgexepath.replace('"%1"', '').strip()
return svgexepath.replace('"', '')
else:
- # TODO: search path
- return os.path.join("/usr/bin", "inkscape")
+ # TODO: search for inkscape in $PATH
+ svgexepath = os.path.join("/usr/bin", "inkscape")
+ if os.path.exists(svgexepath):
+ return svgexepath
+ return None
def open_win_svg(svgexepath, svgfile):
--- a/editors/ConfTreeNodeEditor.py Thu Jan 28 14:51:16 2021 +0000
+++ b/editors/ConfTreeNodeEditor.py Tue Feb 02 13:47:48 2021 +0100
@@ -48,7 +48,7 @@
else:
faces = {
'times': 'Times',
- 'mono': 'Courier',
+ 'mono': 'FreeMono',
'helv': 'Helvetica',
'other': 'new century schoolbook',
'size': 18,
--- a/editors/Viewer.py Thu Jan 28 14:51:16 2021 +0000
+++ b/editors/Viewer.py Tue Feb 02 13:47:48 2021 +0100
@@ -82,7 +82,7 @@
else:
faces = {
'times': 'Times',
- 'mono': 'Courier',
+ 'mono': 'FreeMono',
'helv': 'Helvetica',
'other': 'new century schoolbook',
'size': 10,
--- a/etherlab/EthercatMaster.py Thu Jan 28 14:51:16 2021 +0000
+++ b/etherlab/EthercatMaster.py Tue Feb 02 13:47:48 2021 +0100
@@ -97,6 +97,8 @@
return ((["etherlab_ext"], [(Gen_etherlabfile_path, IECCFLAGS)], True), "",
("runtime_etherlab.py", open(GetLocalPath("runtime_etherlab.py"))))
+ # TODO : rename to match runtime_{location}_extname.py format
+
# --------------------------------------------------
# Ethercat MASTER
# --------------------------------------------------
--- a/etherlab/runtime_etherlab.py Thu Jan 28 14:51:16 2021 +0000
+++ b/etherlab/runtime_etherlab.py Tue Feb 02 13:47:48 2021 +0100
@@ -114,6 +114,7 @@
time.sleep(0.5)
+# TODO : rename to match _runtime_{location}_extname_init() format
def _runtime_etherlab_init():
global KMSGPollThread, StopKMSGThread
StopKMSGThread = False
@@ -121,6 +122,7 @@
KMSGPollThread.start()
+# TODO : rename to match _runtime_{location}_extname_cleanup() format
def _runtime_etherlab_cleanup():
global KMSGPollThread, StopKMSGThread, SDOThread
try:
--- a/modbus/modbus.py Thu Jan 28 14:51:16 2021 +0000
+++ b/modbus/modbus.py Tue Feb 02 13:47:48 2021 +0100
@@ -1257,5 +1257,5 @@
runtimefile.close()
return ([(Gen_MB_c_path, ' -I"' + ModbusPath + '"')], LDFLAGS, True,
- ("runtime_modbus_websettings_%s.py" % location_str, open(runtimefile_path, "rb")),
+ ("runtime_%s_modbus_websettings.py" % location_str, open(runtimefile_path, "rb")),
)
--- a/modbus/web_settings.py Thu Jan 28 14:51:16 2021 +0000
+++ b/modbus/web_settings.py Tue Feb 02 13:47:48 2021 +0100
@@ -183,7 +183,7 @@
("baud" , _("Baud Rate") , ctypes.c_int, MB_Baud ),
("parity" , _("Parity") , ctypes.c_int, MB_Parity ),
("stop_bits" , _("Stop Bits") , ctypes.c_int, MB_StopBits ),
- ("slave_id" , _("Slave ID") , ctypes.c_ulonglong, annotate.Integer)
+ ("slave_id" , _("Slave ID") , ctypes.c_ubyte, annotate.Integer)
]
@@ -228,7 +228,7 @@
# This allows us to confirm the saved data contains the correct addr_type
# when loading from file
save_info = {}
- save_info["addr_type"] = ["addr_type"]
+ save_info["addr_type"] = WebNode_entry["addr_type"]
save_info["node_type"] = WebNode_entry["node_type"]
save_info["config" ] = newConfig
@@ -262,7 +262,7 @@
filename = _WebNodeList[WebNode_id]["filename"]
try:
#if os.path.isfile(filename):
- save_info = json.load(open(filename))
+ save_info = json.load(open(os.path.realpath(filename)))
except Exception:
return None
@@ -526,7 +526,7 @@
-def _runtime_modbus_websettings_%(location_str)s_init():
+def _runtime_%(location_str)s_modbus_websettings_init():
"""
Callback function, called (by PLCObject.py) when a new PLC program
(i.e. XXX.so file) is transfered to the PLC runtime
@@ -609,7 +609,7 @@
-def _runtime_modbus_websettings_%(location_str)s_cleanup():
+def _runtime_%(location_str)s_modbus_websettings_cleanup():
"""
Callback function, called (by PLCObject.py) when a PLC program is unloaded from memory
"""
--- a/plcopen/plcopen.py Thu Jan 28 14:51:16 2021 +0000
+++ b/plcopen/plcopen.py Tue Feb 02 13:47:48 2021 +0100
@@ -331,12 +331,16 @@
def SaveProject(project, filepath):
- project_file = open(filepath, 'w')
- project_file.write(etree.tostring(
+ content = etree.tostring(
project,
pretty_print=True,
xml_declaration=True,
- encoding='utf-8'))
+ encoding='utf-8')
+
+ assert len(content) != 0
+
+ project_file = open(filepath, 'w')
+ project_file.write(content)
project_file.close()
--- a/py_ext/PythonFileCTNMixin.py Thu Jan 28 14:51:16 2021 +0000
+++ b/py_ext/PythonFileCTNMixin.py Tue Feb 02 13:47:48 2021 +0100
@@ -36,6 +36,8 @@
from py_ext.PythonEditor import PythonEditor
+
+
class PythonFileCTNMixin(CodeFile):
CODEFILE_NAME = "PyFile"
@@ -95,19 +97,41 @@
getattr(self.CodeFile, section).getanyText() + "\n" + \
self.PostSectionsTexts.get(section, "")
+ def CTNGlobalInstances(self):
+ variables = self.CodeFileVariables(self.CodeFile)
+ ret = [(variable.getname(),
+ variable.gettype(),
+ variable.getinitial())
+ for variable in variables]
+ location_str = "_".join(map(str, self.GetCurrentLocation()))
+ ret.append(("On_"+location_str+"_Change", "python_poll", ""))
+ return ret
+
+ @staticmethod
+ def GetVarOnChangeContent(var):
+ """
+ returns given variable onchange field
+ function is meant to allow customization
+ """
+ return var.getonchange()
+
def CTNGenerate_C(self, buildpath, locations):
# location string for that CTN
location_str = "_".join(map(str, self.GetCurrentLocation()))
configname = self.GetCTRoot().GetProjectConfigNames()[0]
def _onchangecode(var):
- return '"' + var.getonchange() + \
- "('" + var.getname() + "')\"" \
- if var.getonchange() else '""'
+ result = []
+ for onchangecall in self.GetVarOnChangeContent(var).split(','):
+ onchangecall = onchangecall.strip()
+ if onchangecall:
+ result.append(onchangecall + "('" + var.getname() + "')")
+ return result
+
def _onchange(var):
- return repr(var.getonchange()) \
- if var.getonchange() else None
+ content = self.GetVarOnChangeContent(var)
+ return repr(content) if content else None
pyextname = self.CTNName()
varinfos = map(
@@ -124,6 +148,9 @@
"pyextname": pyextname
},
self.CodeFile.variables.variable)
+
+ onchange_var_count = len([None for varinfo in varinfos if varinfo["onchange"]])
+
# python side PLC global variables access stub
globalstubs = "\n".join([
"""\
@@ -142,8 +169,21 @@
%(desc)s,
%(onchange)s,
%(opts)s))
-""" % varinfo for varinfo in varinfos])
-
+""" % varinfo + ("""
+_PyOnChangeCount_%(name)s = ctypes.c_uint.in_dll(PLCBinary,"__%(name)s_onchange_count")
+_PyOnChangeFirst_%(name)s = _%(name)s_ctype.in_dll(PLCBinary,"__%(name)s_onchange_firstval")
+_PyOnChangeLast_%(name)s = _%(name)s_ctype.in_dll(PLCBinary,"__%(name)s_onchange_lastval")
+""" % varinfo if varinfo["onchange"] else "") for varinfo in varinfos])
+
+ on_change_func_body = "\n".join(["""
+ if _PyOnChangeCount_%(name)s.value > 0:
+ # %(name)s
+ try:""" % varinfo + """
+ """ + """
+ """.join(varinfo['onchangecode'])+"""
+ except Exception as e:
+ errors.append("%(name)s: "+str(e))
+""" % varinfo for varinfo in varinfos if varinfo["onchange"]])
# Runtime calls (start, stop, init, and cleanup)
rtcalls = ""
for section in self.SECTIONS_NAMES:
@@ -163,6 +203,9 @@
"globalstubs": globalstubs,
"globalsection": globalsection,
"rtcalls": rtcalls,
+ "location_str": location_str,
+ "on_change_func_body":on_change_func_body,
+ "onchange_var_count": onchange_var_count
}
PyFileContent = """\
@@ -174,6 +217,11 @@
## Code for PLC global variable access
from runtime.typemapping import TypeTranslator
import ctypes
+
+_PySafeGetChanges_%(pyextname)s = PLCBinary.PySafeGetChanges_%(location_str)s
+_PySafeGetChanges_%(pyextname)s.restype = None
+_PySafeGetChanges_%(pyextname)s.argtypes = None
+
_%(pyextname)sGlobalsDesc = []
__ext_name__ = "%(pyextname)s"
PLCGlobalsDesc.append(( "%(pyextname)s" , _%(pyextname)sGlobalsDesc ))
@@ -185,6 +233,13 @@
## Beremiz python runtime calls
%(rtcalls)s
+def On_%(pyextname)s_Change():
+ _PySafeGetChanges_%(pyextname)s()
+ errors = []
+%(on_change_func_body)s
+ if len(errors)>0 :
+ raise Exception("Exception in %(pyextname)s OnChange call:\\\\n" + "\\\\n".join(errors))
+
del __ext_name__
""" % loc_dict
@@ -220,7 +275,12 @@
"""
vardeconchangefmt = """\
-PYTHON_POLL* __%(name)s_notifier;
+unsigned int __%(name)s_rbuffer_written = 0;
+IEC_%(IECtype)s __%(name)s_rbuffer_firstval;
+IEC_%(IECtype)s __%(name)s_rbuffer_lastval;
+unsigned int __%(name)s_onchange_count = 0;
+IEC_%(IECtype)s __%(name)s_onchange_firstval;
+IEC_%(IECtype)s __%(name)s_onchange_lastval;
"""
varretfmt = """\
@@ -243,16 +303,27 @@
if(!AtomicCompareExchange(&__%(name)s_rlock, 0, 1)){
IEC_%(IECtype)s tmp = __GET_VAR(%(configname)s__%(uppername)s);
if(NE_%(IECtype)s(1, NULL, __%(name)s_rbuffer, tmp)){
+ if(__%(name)s_rbuffer_written == 0);
+ __%(name)s_rbuffer_firstval = __%(name)s_rbuffer;
+ __%(name)s_rbuffer_lastval = tmp;
__%(name)s_rbuffer = tmp;
- PYTHON_POLL_body__(__%(name)s_notifier);
+ /* count one more change */
+ __%(name)s_rbuffer_written += 1;
+ some_change_found = 1;
}
AtomicCompareExchange((long*)&__%(name)s_rlock, 1, 0);
}
"""
- varinitonchangefmt = """\
- __%(name)s_notifier = __GET_GLOBAL_ON%(uppername)sCHANGE();
- __SET_VAR(__%(name)s_notifier->,TRIG,,__BOOL_LITERAL(TRUE));
- __SET_VAR(__%(name)s_notifier->,CODE,,__STRING_LITERAL(%(onchangelen)d,%(onchangecode)s));
+
+ varcollectchangefmt = """\
+ while(AtomicCompareExchange(&__%(name)s_rlock, 0, 1));
+ __%(name)s_onchange_count = __%(name)s_rbuffer_written;
+ __%(name)s_onchange_firstval = __%(name)s_rbuffer_firstval;
+ __%(name)s_onchange_lastval = __%(name)s_rbuffer_lastval;
+ /* mark variable as unchanged */
+ __%(name)s_rbuffer_written = 0;
+ AtomicCompareExchange((long*)&__%(name)s_rlock, 1, 0);
+
"""
vardec = "\n".join([(vardecfmt + vardeconchangefmt
if varinfo["onchange"] else vardecfmt) % varinfo
@@ -261,16 +332,20 @@
varpub = "\n".join([(varpubonchangefmt if varinfo["onchange"] else
varpubfmt) % varinfo
for varinfo in varinfos])
- varinit = "\n".join([varinitonchangefmt %
- dict(onchangelen=len(varinfo["onchangecode"]), **varinfo)
+ varcollectchange = "\n".join([varcollectchangefmt % varinfo
for varinfo in varinfos if varinfo["onchange"]])
+ pysafe_pypoll_code = "On_"+pyextname+"_Change()"
+
loc_dict = {
"vardec": vardec,
- "varinit": varinit,
"varret": varret,
"varpub": varpub,
"location_str": location_str,
+ "pysafe_pypoll_code": '"'+pysafe_pypoll_code+'"',
+ "pysafe_pypoll_code_len": len(pysafe_pypoll_code),
+ "varcollectchange": varcollectchange,
+ "onchange_var_count": onchange_var_count
}
# TODO : use config name obtained from model instead of default
@@ -286,12 +361,17 @@
#include "config.h"
#include "beremiz.h"
+PYTHON_POLL* __%(location_str)s_notifier;
+
/* User variables reference */
%(vardec)s
/* Beremiz confnode functions */
int __init_%(location_str)s(int argc,char **argv){
-%(varinit)s
+ __%(location_str)s_notifier = __GET_GLOBAL_ON_%(location_str)s_CHANGE();
+ __SET_VAR(__%(location_str)s_notifier->,TRIG,,__BOOL_LITERAL(TRUE));
+ __SET_VAR(__%(location_str)s_notifier->,CODE,,__STRING_LITERAL(%(pysafe_pypoll_code_len)d,%(pysafe_pypoll_code)s));
+
return 0;
}
@@ -302,9 +382,22 @@
%(varret)s
}
+static int passing_changes_to_python = 0;
void __publish_%(location_str)s(void){
+ int some_change_found = 0;
%(varpub)s
-}
+ passing_changes_to_python |= some_change_found;
+ // call python part if there was at least a change
+ if(passing_changes_to_python){
+ PYTHON_POLL_body__(__%(location_str)s_notifier);
+ passing_changes_to_python &= !(__GET_VAR(__%(location_str)s_notifier->ACK,));
+ }
+}
+
+void* PySafeGetChanges_%(location_str)s(void){
+%(varcollectchange)s
+}
+
""" % loc_dict
Gen_PyCfile_path = os.path.join(buildpath, "PyCFile_%s.c" % location_str)
--- a/runtime/NevowServer.py Thu Jan 28 14:51:16 2021 +0000
+++ b/runtime/NevowServer.py Tue Feb 02 13:47:48 2021 +0100
@@ -39,6 +39,7 @@
import util.paths as paths
from runtime.loglevels import LogLevels, LogLevelsDict
+from runtime import MainWorker, GetPLCObjectSingleton
PAGE_TITLE = 'Beremiz Runtime Web Interface'
@@ -217,6 +218,18 @@
"Send a message to the log"),
action=_("Send"))
+ # pylint: disable=no-self-argument
+ def restartOrRepairPLC(
+ ctx=annotate.Context(),
+ action=annotate.Choice(["Restart", "Repair"],
+ required=True,
+ label=_("Action"))):
+ pass
+
+ restartOrRepairPLC = annotate.autocallable(restartOrRepairPLC,
+ label=_(
+ "Restart or Repair"),
+ action=_("Do"))
customSettingsURLs = {
}
@@ -284,9 +297,16 @@
def sendLogMessage(self, level, message, **kwargs):
level = LogLevelsDict[level]
- if _PySrv.plcobj is not None:
- _PySrv.plcobj.LogMessage(
- level, "Web form log message: " + message)
+ GetPLCObjectSingleton().LogMessage(
+ level, "Web form log message: " + message)
+
+ def restartOrRepairPLC(self, action, **kwargs):
+ if(action == "Repair"):
+ GetPLCObjectSingleton().RepairPLC()
+ else:
+ MainWorker.quit()
+
+
def locateChild(self, ctx, segments):
if segments[0] in customSettingsURLs:
--- a/runtime/PLCObject.py Thu Jan 28 14:51:16 2021 +0000
+++ b/runtime/PLCObject.py Tue Feb 02 13:47:48 2021 +0100
@@ -360,8 +360,18 @@
v = parent.python_runtime_vars["_"+name+"_pack"](t, value)
parent.python_runtime_vars["_PySafeSetPLCGlob_"+name](ctypes.byref(v))
+ class OnChangeStateClass(object):
+ def __getattr__(self, name):
+ u = parent.python_runtime_vars["_"+name+"_unpack"]
+ return type("changedesc",(),dict(
+ count = parent.python_runtime_vars["_PyOnChangeCount_"+name].value,
+ first = u(parent.python_runtime_vars["_PyOnChangeFirst_"+name]),
+ last = u(parent.python_runtime_vars["_PyOnChangeLast_"+name])))
+
+
self.python_runtime_vars.update({
"PLCGlobals": PLCSafeGlobals(),
+ "OnChange": OnChangeStateClass(),
"WorkingDir": self.workingdir,
"PLCObject": self,
"PLCBinary": self.PLClibraryHandle,
@@ -440,6 +450,7 @@
if cmd == "Activate":
self.PythonRuntimeCall("start")
+ self.PreStartPLC()
self.PythonThreadLoop()
self.PythonRuntimeCall("stop", reverse_order=True)
else: # "Finish"
@@ -471,8 +482,6 @@
if not self.LoadPLC():
self._fail(_("Problem starting PLC : can't load PLC"))
- self.PreStartPLC()
-
if self.CurrentPLCFilename is not None and self.PLCStatus == PlcStatus.Stopped:
c_argv = ctypes.c_char_p * len(self.argv)
res = self._startPLC(len(self.argv), c_argv(*self.argv))
@@ -546,12 +555,15 @@
os.close(fd)
self._init_blobs()
- def _BlobAsFile(self, blobID, newpath):
+ def BlobAsFile(self, blobID, newpath):
blob = self.blobs.pop(blobID, None)
if blob is None:
raise Exception(_("Missing data to create file: {}").format(newpath))
+ self._BlobAsFile(blob, newpath)
+
+ def _BlobAsFile(self, blob, newpath):
fd, path, _md5sum = blob
fobj = os.fdopen(fd)
fobj.flush()
@@ -610,13 +622,13 @@
try:
# Create new PLC file
- self._BlobAsFile(plc_object, new_PLC_filename)
+ self.BlobAsFile(plc_object, new_PLC_filename)
# Then write the files
log = open(extra_files_log, "w")
for fname, blobID in extrafiles:
fpath = os.path.join(self.workingdir, fname)
- self._BlobAsFile(blobID, fpath)
+ self.BlobAsFile(blobID, fpath)
log.write(fname+'\n')
# Store new PLC filename based on md5 key
--- a/targets/plc_debug.c Thu Jan 28 14:51:16 2021 +0000
+++ b/targets/plc_debug.c Tue Feb 02 13:47:48 2021 +0100
@@ -100,7 +100,7 @@
void __init_debug(void)
{
/* init local static vars */
-#ifndef TARGET_ONLINE_DEBUG_DISABLE
+#ifndef TARGET_ONLINE_DEBUG_DISABLE
buffer_cursor = debug_buffer;
buffer_state = BUFFER_FREE;
#endif
@@ -109,9 +109,9 @@
InitRetain();
/* Iterate over all variables to fill debug buffer */
if(CheckRetainBuffer()){
- __for_each_variable_do(RemindIterator);
+ __for_each_variable_do(RemindIterator);
}else{
- char mstr[] = "RETAIN memory invalid - defaults used";
+ char mstr[] = "RETAIN memory invalid - defaults used";
LogMessage(LOG_WARNING, mstr, sizeof(mstr));
}
retain_offset = 0;
@@ -124,7 +124,7 @@
void __cleanup_debug(void)
{
-#ifndef TARGET_ONLINE_DEBUG_DISABLE
+#ifndef TARGET_ONLINE_DEBUG_DISABLE
buffer_cursor = debug_buffer;
InitiateDebugTransfer();
#endif
@@ -150,16 +150,14 @@
if(flags & ( __IEC_DEBUG_FLAG | __IEC_RETAIN_FLAG)){
USINT size = __get_type_enum_size(dsc->type);
-#ifndef TARGET_ONLINE_DEBUG_DISABLE
+#ifndef TARGET_ONLINE_DEBUG_DISABLE
if(flags & __IEC_DEBUG_FLAG){
/* copy visible variable to buffer */;
if(do_debug){
/* compute next cursor positon.
No need to check overflow, as BUFFER_SIZE
is computed large enough */
- if((dsc->type == STRING_ENUM) ||
- (dsc->type == STRING_P_ENUM) ||
- (dsc->type == STRING_O_ENUM)){
+ if(__Is_a_string(dsc)){
/* optimization for strings */
size = ((STRING*)visible_value_p)->len + 1;
}
@@ -174,7 +172,7 @@
memcpy(real_value_p, visible_value_p, size);
}
}
-#endif
+#endif
if(flags & __IEC_RETAIN_FLAG){
/* compute next cursor positon*/
--- a/targets/var_access.c Thu Jan 28 14:51:16 2021 +0000
+++ b/targets/var_access.c Tue Feb 02 13:47:48 2021 +0100
@@ -14,6 +14,10 @@
forced_value_p = &((__IEC_##TYPENAME##_p *)varp)->fvalue;\
break;
+#define __Is_a_string(dsc) (dsc->type == STRING_ENUM) ||\
+ (dsc->type == STRING_P_ENUM) ||\
+ (dsc->type == STRING_O_ENUM)
+
static void* UnpackVar(__Unpack_desc_type *dsc, void **real_value_p, char *flags)
{
void *varp = dsc->ptr;
--- a/tests/python/plc.xml Thu Jan 28 14:51:16 2021 +0000
+++ b/tests/python/plc.xml Tue Feb 02 13:47:48 2021 +0100
@@ -1,7 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<project xmlns="http://www.plcopen.org/xml/tc6_0201" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xhtml="http://www.w3.org/1999/xhtml" xsi:schemaLocation="http://www.plcopen.org/xml/tc6_0201">
<fileHeader companyName="" productName="Beremiz" productVersion="0.0" creationDateTime="2008-12-14T16:21:19" contentDescription="This example shows many features in Beremiz: 1. How to implement python extensions. 2. How to implement basic C extension. 3. How to use C code in IEC POUs. 4. How to call C functions from python code. 5. How to avoid race conditions between IEC, C and python code. 6. How to convert betweet different IEC types. "/>
- <contentHeader name="Beremiz Python Support Tests" modificationDateTime="2020-06-17T13:19:14">
+ <contentHeader name="Beremiz Python Support Tests" modificationDateTime="2020-10-19T23:53:08">
<coordinateInfo>
<pageSize x="1024" y="1024"/>
<fbd>
--- a/tests/python/py_ext_0@py_ext/pyfile.xml Thu Jan 28 14:51:16 2021 +0000
+++ b/tests/python/py_ext_0@py_ext/pyfile.xml Tue Feb 02 13:47:48 2021 +0100
@@ -1,8 +1,8 @@
<?xml version='1.0' encoding='utf-8'?>
<PyFile xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<variables>
- <variable name="SomeVarName" type="DINT" onchange="MyFunc"/>
- <variable name="Grumpf" type="STRING" initial="'mhoo'" onchange="MyFunc"/>
+ <variable name="SomeVarName" type="DINT" onchange="MyFunc, SomeChange"/>
+ <variable name="Grumpf" type="STRING" initial="'mhoo'" onchange="MyFunc, MyOtherFunc"/>
</variables>
<globals>
<xhtml:p><![CDATA[
@@ -12,6 +12,15 @@
def MyFunc(*args):
print args
+def MyOtherFunc(*args):
+ print "other", args
+
+def SomeChange(*args):
+ print "count",OnChange.SomeVarName.count
+ print "first",OnChange.SomeVarName.first
+ print "last",OnChange.SomeVarName.last
+
+
]]></xhtml:p>
</globals>
<init>
--- a/util/ProcessLogger.py Thu Jan 28 14:51:16 2021 +0000
+++ b/util/ProcessLogger.py Tue Feb 02 13:47:48 2021 +0100
@@ -78,7 +78,6 @@
timeout=None, outlimit=None, errlimit=None,
endlog=None, keyword=None, kill_it=False, cwd=None,
encoding=None, output_encoding=None):
- assert(logger)
self.logger = logger
if not isinstance(Command, list):
self.Command_str = Command
@@ -160,7 +159,7 @@
v = v.decode(self.output_encoding)
self.outdata.append(v)
self.outlen += 1
- if not self.no_stdout:
+ if self.logger and not self.no_stdout:
self.logger.write(v)
if (self.keyword and v.find(self.keyword) != -1) or (self.outlimit and self.outlen > self.outlimit):
self.endlog()
@@ -170,7 +169,7 @@
v = v.decode(self.output_encoding)
self.errdata.append(v)
self.errlen += 1
- if not self.no_stderr:
+ if self.logger and not self.no_stderr:
self.logger.write_warning(v)
if self.errlimit and self.errlen > self.errlimit:
self.endlog()