# HG changeset patch # User Edouard Tisserant # Date 1610963968 -3600 # Node ID 745b64e7c695e96473efd2bbca405c6b56bfd906 # Parent 76e8ec46828a1d5390cd2c92cb4eab07ae7b0b74# Parent a00f41d097f369789f923c8dbcd5dc07cd07e375 Merge diff -r 76e8ec46828a -r 745b64e7c695 BeremizIDE.py --- a/BeremizIDE.py Fri Dec 25 17:12:02 2020 +0000 +++ b/BeremizIDE.py Mon Jan 18 10:59:28 2021 +0100 @@ -103,7 +103,7 @@ } else: faces = { - 'mono': 'Courier', + 'mono': 'FreeMono', 'size': 10, } diff -r 76e8ec46828a -r 745b64e7c695 CodeFileTreeNode.py --- a/CodeFileTreeNode.py Fri Dec 25 17:12:02 2020 +0000 +++ b/CodeFileTreeNode.py Mon Jan 18 10:59:28 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): diff -r 76e8ec46828a -r 745b64e7c695 ConfigTreeNode.py --- a/ConfigTreeNode.py Fri Dec 25 17:12:02 2020 +0000 +++ b/ConfigTreeNode.py Mon Jan 18 10:59:28 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(""" @@ -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 diff -r 76e8ec46828a -r 745b64e7c695 ProjectController.py --- a/ProjectController.py Fri Dec 25 17:12:02 2020 +0000 +++ b/ProjectController.py Mon Jan 18 10:59:28 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) diff -r 76e8ec46828a -r 745b64e7c695 XSLTransform.py --- a/XSLTransform.py Fri Dec 25 17:12:02 2020 +0000 +++ b/XSLTransform.py Mon Jan 18 10:59:28 2021 +0100 @@ -22,4 +22,7 @@ # print(self.xslt.error_log) return res + def get_error_log(self): + return self.xslt.error_log + diff -r 76e8ec46828a -r 745b64e7c695 bacnet/bacnet.py --- a/bacnet/bacnet.py Fri Dec 25 17:12:02 2020 +0000 +++ b/bacnet/bacnet.py Mon Jan 18 10:59:28 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) diff -r 76e8ec46828a -r 745b64e7c695 bacnet/web_settings.py --- a/bacnet/web_settings.py Fri Dec 25 17:12:02 2020 +0000 +++ b/bacnet/web_settings.py Mon Jan 18 10:59:28 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 """ diff -r 76e8ec46828a -r 745b64e7c695 controls/CustomStyledTextCtrl.py --- a/controls/CustomStyledTextCtrl.py Fri Dec 25 17:12:02 2020 +0000 +++ b/controls/CustomStyledTextCtrl.py Mon Jan 18 10:59:28 2021 +0100 @@ -40,7 +40,7 @@ else: faces = { 'times': 'Times', - 'mono': 'Courier', + 'mono': 'FreeMono', 'helv': 'Helvetica', 'other': 'new century schoolbook', 'size': 12, diff -r 76e8ec46828a -r 745b64e7c695 controls/LogViewer.py --- a/controls/LogViewer.py Fri Dec 25 17:12:02 2020 +0000 +++ b/controls/LogViewer.py Mon Jan 18 10:59:28 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) diff -r 76e8ec46828a -r 745b64e7c695 docutil/docsvg.py --- a/docutil/docsvg.py Fri Dec 25 17:12:02 2020 +0000 +++ b/docutil/docsvg.py Mon Jan 18 10:59:28 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): diff -r 76e8ec46828a -r 745b64e7c695 editors/ConfTreeNodeEditor.py --- a/editors/ConfTreeNodeEditor.py Fri Dec 25 17:12:02 2020 +0000 +++ b/editors/ConfTreeNodeEditor.py Mon Jan 18 10:59:28 2021 +0100 @@ -48,7 +48,7 @@ else: faces = { 'times': 'Times', - 'mono': 'Courier', + 'mono': 'FreeMono', 'helv': 'Helvetica', 'other': 'new century schoolbook', 'size': 18, diff -r 76e8ec46828a -r 745b64e7c695 editors/Viewer.py --- a/editors/Viewer.py Fri Dec 25 17:12:02 2020 +0000 +++ b/editors/Viewer.py Mon Jan 18 10:59:28 2021 +0100 @@ -82,7 +82,7 @@ else: faces = { 'times': 'Times', - 'mono': 'Courier', + 'mono': 'FreeMono', 'helv': 'Helvetica', 'other': 'new century schoolbook', 'size': 10, diff -r 76e8ec46828a -r 745b64e7c695 etherlab/EthercatMaster.py --- a/etherlab/EthercatMaster.py Fri Dec 25 17:12:02 2020 +0000 +++ b/etherlab/EthercatMaster.py Mon Jan 18 10:59:28 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 # -------------------------------------------------- diff -r 76e8ec46828a -r 745b64e7c695 etherlab/runtime_etherlab.py --- a/etherlab/runtime_etherlab.py Fri Dec 25 17:12:02 2020 +0000 +++ b/etherlab/runtime_etherlab.py Mon Jan 18 10:59:28 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: diff -r 76e8ec46828a -r 745b64e7c695 modbus/modbus.py --- a/modbus/modbus.py Fri Dec 25 17:12:02 2020 +0000 +++ b/modbus/modbus.py Mon Jan 18 10:59:28 2021 +0100 @@ -1058,5 +1058,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")), ) diff -r 76e8ec46828a -r 745b64e7c695 modbus/web_settings.py --- a/modbus/web_settings.py Fri Dec 25 17:12:02 2020 +0000 +++ b/modbus/web_settings.py Mon Jan 18 10:59:28 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 """ diff -r 76e8ec46828a -r 745b64e7c695 plcopen/plcopen.py --- a/plcopen/plcopen.py Fri Dec 25 17:12:02 2020 +0000 +++ b/plcopen/plcopen.py Mon Jan 18 10:59:28 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() diff -r 76e8ec46828a -r 745b64e7c695 py_ext/PythonFileCTNMixin.py --- a/py_ext/PythonFileCTNMixin.py Fri Dec 25 17:12:02 2020 +0000 +++ b/py_ext/PythonFileCTNMixin.py Mon Jan 18 10:59:28 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) diff -r 76e8ec46828a -r 745b64e7c695 runtime/NevowServer.py --- a/runtime/NevowServer.py Fri Dec 25 17:12:02 2020 +0000 +++ b/runtime/NevowServer.py Mon Jan 18 10:59:28 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: diff -r 76e8ec46828a -r 745b64e7c695 runtime/PLCObject.py --- a/runtime/PLCObject.py Fri Dec 25 17:12:02 2020 +0000 +++ b/runtime/PLCObject.py Mon Jan 18 10:59:28 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 diff -r 76e8ec46828a -r 745b64e7c695 targets/plc_debug.c --- a/targets/plc_debug.c Fri Dec 25 17:12:02 2020 +0000 +++ b/targets/plc_debug.c Mon Jan 18 10:59:28 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*/ diff -r 76e8ec46828a -r 745b64e7c695 targets/var_access.c --- a/targets/var_access.c Fri Dec 25 17:12:02 2020 +0000 +++ b/targets/var_access.c Mon Jan 18 10:59:28 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; diff -r 76e8ec46828a -r 745b64e7c695 tests/python/plc.xml --- a/tests/python/plc.xml Fri Dec 25 17:12:02 2020 +0000 +++ b/tests/python/plc.xml Mon Jan 18 10:59:28 2021 +0100 @@ -1,7 +1,7 @@ - + diff -r 76e8ec46828a -r 745b64e7c695 tests/python/py_ext_0@py_ext/pyfile.xml --- a/tests/python/py_ext_0@py_ext/pyfile.xml Fri Dec 25 17:12:02 2020 +0000 +++ b/tests/python/py_ext_0@py_ext/pyfile.xml Mon Jan 18 10:59:28 2021 +0100 @@ -1,8 +1,8 @@ - - + + diff -r 76e8ec46828a -r 745b64e7c695 util/ProcessLogger.py --- a/util/ProcessLogger.py Fri Dec 25 17:12:02 2020 +0000 +++ b/util/ProcessLogger.py Mon Jan 18 10:59:28 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()