# HG changeset patch # User Edouard Tisserant # Date 1520006485 -3600 # Node ID cc7a469534712c604a0cb64453c6d5603bc8414b # Parent b4a1ba9dbaf32f8201a063c42912c985ba012e1f# Parent 38e912c8bd317b30c5db18f91d9f0a4083cdcd5d merged Sergeys changes diff -r 38e912c8bd31 -r cc7a46953471 Beremiz.py --- a/Beremiz.py Mon Feb 19 19:36:43 2018 +0300 +++ b/Beremiz.py Fri Mar 02 17:01:25 2018 +0100 @@ -28,10 +28,9 @@ import os import sys import getopt -import time import wx -from wx.lib.agw.advancedsplash import AdvancedSplash +from wx.lib.agw.advancedsplash import AdvancedSplash, AS_NOTIMEOUT, AS_CENTER_ON_SCREEN import util.paths as paths @@ -47,24 +46,13 @@ self.buildpath = None self.splash = None self.splashPath = self.Bpath("images", "splash.png") + self.modules = ["BeremizIDE"] + self.debug = os.path.exists("BEREMIZ_DEBUG") + self.handle_exception = None def Bpath(self, *args): return os.path.join(self.app_dir, *args) - def ShowSplashScreen(self): - bmp = wx.Image(self.splashPath).ConvertToBitmap() - self.splash = AdvancedSplash(None, bitmap=bmp) - - # process all events - # even the events generated by splash themself during showing - if wx.Platform == '__WXMSW__': - self.splash.Show() - self.splash.ProcessEvent(wx.PaintEvent()) - else: - for dummy in range(0, 30): - wx.Yield() - time.sleep(0.01) - def Usage(self): print("Usage:") print("%s [Options] [Projectpath] [Buildpath]" % sys.argv[0]) @@ -113,32 +101,46 @@ self.buildpath = args[1] def CreateApplication(self): - BMZ_DBG = os.path.exists("BEREMIZ_DEBUG") - - if wx.VERSION >= (3, 0, 0): - self.app = wx.App(redirect=BMZ_DBG) - else: - self.app = wx.PySimpleApp(redirect=BMZ_DBG) - + + BeremizAppType = wx.App if wx.VERSION >= (3, 0, 0) else wx.PySimpleApp + + class BeremizApp(BeremizAppType): + def OnInit(_self): # pylint: disable=no-self-argument + self.ShowSplashScreen() + return True + + self.app = BeremizApp(redirect=self.debug) self.app.SetAppName('beremiz') if wx.VERSION < (3, 0, 0): wx.InitAllImageHandlers() - self.ShowSplashScreen() - self.BackgroundInitialization() + def ShowSplashScreen(self): + class Splash(AdvancedSplash): + def OnPaint(_self, event): # pylint: disable=no-self-argument + AdvancedSplash.OnPaint(_self, event) + wx.CallAfter(self.AppStart) + bmp = wx.Image(self.splashPath).ConvertToBitmap() + self.splash = Splash(None, + bitmap=bmp, + agwStyle=AS_NOTIMEOUT | AS_CENTER_ON_SCREEN) def BackgroundInitialization(self): self.InitI18n() self.CheckUpdates() self.LoadExtensions() self.ImportModules() - self.InstallExceptionHandler() - self.ShowUI() def InitI18n(self): from util.misc import InstallLocalRessources InstallLocalRessources(self.app_dir) + def globals(self): + """ + allows customizations to specify what globals + are passed to extensions + """ + return globals() + def LoadExtensions(self): for extfilename in self.extensions: from util.TranslationCatalogs import AddCatalog @@ -147,7 +149,7 @@ sys.path.append(extension_folder) AddCatalog(os.path.join(extension_folder, "locale")) AddBitmapFolder(os.path.join(extension_folder, "images")) - execfile(extfilename, locals()) + execfile(extfilename, self.globals()) def CheckUpdates(self): if self.updateinfo_url is not None: @@ -168,30 +170,49 @@ self.splash.SetText(text=self.updateinfo) def ImportModules(self): - global BeremizIDE - import BeremizIDE + for modname in self.modules: + mod = __import__(modname) + setattr(self, modname, mod) def InstallExceptionHandler(self): import version import util.ExceptionHandler - util.ExceptionHandler.AddExceptHook(version.app_version) - - def ShowUI(self): - import BeremizIDE - self.frame = BeremizIDE.Beremiz(None, self.projectOpen, self.buildpath) + self.handle_exception = util.ExceptionHandler.AddExceptHook(version.app_version) + + def CreateUI(self): + self.frame = self.BeremizIDE.Beremiz(None, self.projectOpen, self.buildpath) + + def CloseSplash(self): if self.splash: self.splash.Close() + + def ShowUI(self): self.frame.Show() def PreStart(self): self.ProcessCommandLineArgs() self.CreateApplication() + def AppStart(self): + try: + self.BackgroundInitialization() + self.CreateUI() + self.CloseSplash() + self.ShowUI() + except (KeyboardInterrupt, SystemExit): + raise + except Exception: + if self.handle_exception is not None: + self.handle_exception(*sys.exc_info(), exit=True) + else: + raise + def MainLoop(self): self.app.MainLoop() def Start(self): self.PreStart() + self.InstallExceptionHandler() self.MainLoop() diff -r 38e912c8bd31 -r cc7a46953471 BeremizIDE.py --- a/BeremizIDE.py Mon Feb 19 19:36:43 2018 +0300 +++ b/BeremizIDE.py Fri Mar 02 17:01:25 2018 +0100 @@ -56,7 +56,8 @@ from controls import EnhancedStatusBar as esb from dialogs.AboutDialog import ShowAboutDialog -from PLCControler import \ +from plcopen.types_enums import \ + ComputeConfigurationName, \ LOCATION_CONFNODE, \ LOCATION_MODULE, \ LOCATION_GROUP, \ @@ -64,9 +65,10 @@ LOCATION_VAR_OUTPUT, \ LOCATION_VAR_MEMORY, \ ITEM_PROJECT, \ - ITEM_RESOURCE - -from ProjectController import ProjectController, GetAddMenuItems, MATIEC_ERROR_MODEL, ITEM_CONFNODE + ITEM_RESOURCE, \ + ITEM_CONFNODE + +from ProjectController import ProjectController, GetAddMenuItems, MATIEC_ERROR_MODEL from IDEFrame import \ TITLE,\ @@ -455,7 +457,7 @@ self.ProjectTree.Enable(True) self.PouInstanceVariablesPanel.SetController(self.Controler) self.RefreshConfigRecentProjects(os.path.abspath(projectOpen)) - self._Refresh(PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE) + self.RefreshAfterLoad() else: self.ResetView() self.ShowErrorMessage(result) @@ -463,10 +465,11 @@ self.CTR = ctr self.Controler = ctr if ctr is not None: + ctr.SetAppFrame(self, self.Log) self.LibraryPanel.SetController(self.Controler) self.ProjectTree.Enable(True) self.PouInstanceVariablesPanel.SetController(self.Controler) - self._Refresh(PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE) + self.RefreshAfterLoad() if self.EnableDebug: self.DebugVariablePanel.SetDataProducer(self.CTR) @@ -823,7 +826,6 @@ def ResetView(self): IDEFrame.ResetView(self) - self.ConfNodeInfos = {} if self.CTR is not None: self.CTR.CloseProject() self.CTR = None @@ -877,7 +879,7 @@ self.RefreshConfigRecentProjects(projectpath) if self.EnableDebug: self.DebugVariablePanel.SetDataProducer(self.CTR) - self._Refresh(PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE) + self.RefreshAfterLoad() IDEFrame.OnAddNewProject(self, event) else: self.ResetView() @@ -916,7 +918,7 @@ self.PouInstanceVariablesPanel.SetController(self.Controler) if self.EnableDebug: self.DebugVariablePanel.SetDataProducer(self.CTR) - self._Refresh(PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE) + self.RefreshAfterLoad() else: self.ResetView() self.ShowErrorMessage(result) @@ -936,6 +938,13 @@ self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU) self.RefreshAll() + def RefreshAfterLoad(self): + self._Refresh(PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE) + + def RefreshAfterSave(self): + self.RefreshAll() + self._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES) + def OnSaveProjectMenu(self, event): selected = self.TabsOpened.GetSelection() if selected != -1: @@ -943,8 +952,7 @@ window.Save() if self.CTR is not None: self.CTR.SaveProject() - self.RefreshAll() - self._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES) + self.RefreshAfterSave() def OnSaveProjectAsMenu(self, event): selected = self.TabsOpened.GetSelection() @@ -953,9 +961,8 @@ window.SaveAs() if self.CTR is not None: self.CTR.SaveProjectAs() - self.RefreshAll() + self.RefreshAfterSave() self.RefreshConfigRecentProjects(self.CTR.ProjectPath) - self._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES) def OnQuitMenu(self, event): self.Close() @@ -1088,7 +1095,7 @@ def ShowHighlight(self, infos, start, end, highlight_type): config_name = self.Controler.GetProjectMainConfigurationName() - if config_name is not None and infos[0] == self.Controler.ComputeConfigurationName(config_name): + if config_name is not None and infos[0] == ComputeConfigurationName(config_name): self.CTR._OpenView() selected = self.TabsOpened.GetSelection() if selected != -1: diff -r 38e912c8bd31 -r cc7a46953471 Beremiz_service.py --- a/Beremiz_service.py Mon Feb 19 19:36:43 2018 +0300 +++ b/Beremiz_service.py Fri Mar 02 17:01:25 2018 +0100 @@ -50,8 +50,9 @@ -a - autostart PLC (0:disable 1:enable) (default:0) -x - enable/disable wxTaskbarIcon (0:disable 1:enable) (default:1) -t - enable/disable Twisted web interface (0:disable 1:enable) (default:1) - -w - web server port or "off" (default:8009) - -c - WAMP client config file or "off" (default:wampconf.json) + -w - web server port or "off" to disable web server (default:8009) + -c - WAMP client default config file (default:wampconf.json) + -s - WAMP client secret, given as a file -e - python extension (absolute path .py) working_dir - directory where are stored PLC files @@ -59,7 +60,7 @@ try: - opts, argv = getopt.getopt(sys.argv[1:], "i:p:n:x:t:a:w:c:e:h") + opts, argv = getopt.getopt(sys.argv[1:], "i:p:n:x:t:a:w:c:e:s:h") except getopt.GetoptError, err: # print help information and exit: print(str(err)) # will print something like "option -a not recognized" @@ -70,7 +71,8 @@ given_ip = None port = 3000 webport = 8009 -wampconf = "wampconf.json" +wampsecret = None +wampconf = None servicename = None autostart = False enablewx = True @@ -105,8 +107,12 @@ webport = None if a == "off" else int(a) elif o == "-c": wampconf = None if a == "off" else a + elif o == "-s": + wampsecret = None if a == "off" else a elif o == "-e": - extensions.append(a) + fnameanddirname = list(os.path.split(os.path.realpath(a))) + fnameanddirname.reverse() + extensions.append(fnameanddirname) else: usage() sys.exit() @@ -167,6 +173,8 @@ # __builtin__.__dict__['_'] = wx.GetTranslation +# Life is hard... have a candy. +# pylint: disable=wrong-import-position,wrong-import-order if enablewx: try: import wx @@ -319,20 +327,20 @@ _("IP is not valid!"))]) if dlg.ShowModal() == wx.ID_OK: self.pyroserver.ip_addr = dlg.GetValue() - self.pyroserver.Stop() + self.pyroserver.Restart() def OnTaskBarChangePort(self, evt): dlg = ParamsEntryDialog(None, _("Enter a port number "), defaultValue=str(self.pyroserver.port)) dlg.SetTests([(UnicodeType.isdigit, _("Port number must be an integer!")), (lambda port: 0 <= int(port) <= 65535, _("Port number must be 0 <= port <= 65535!"))]) if dlg.ShowModal() == wx.ID_OK: self.pyroserver.port = int(dlg.GetValue()) - self.pyroserver.Stop() + self.pyroserver.Restart() def OnTaskBarChangeWorkingDir(self, evt): dlg = wx.DirDialog(None, _("Choose a working directory "), self.pyroserver.workdir, wx.DD_NEW_DIR_BUTTON) if dlg.ShowModal() == wx.ID_OK: self.pyroserver.workdir = dlg.GetPath() - self.pyroserver.Stop() + self.pyroserver.Restart() def OnTaskBarChangeName(self, evt): servicename = self.pyroserver.servicename @@ -412,24 +420,31 @@ def Loop(self): while self.continueloop: + pyro.initServer() + self.daemon = pyro.Daemon(host=self.ip_addr, port=self.port) + # pyro never frees memory after connection close if no timeout set + # taking too small timeout value may cause + # unwanted diconnection when IDE is kept busy for long periods + self.daemon.setTimeout(60) self.Start() + self.daemon.requestLoop() + self.daemon.sock.close() def Restart(self): - self.Stop() + self._stop() def Quit(self): self.continueloop = False if self.plcobj is not None: self.plcobj.StopPLC() self.plcobj.UnLoadPLC() - self.Stop() + self._stop() def Start(self): - pyro.initServer() - self.daemon = pyro.Daemon(host=self.ip_addr, port=self.port) self.plcobj = PLCObject(self.workdir, self.daemon, self.argv, self.statuschange, self.evaluator, self.pyruntimevars) + uri = self.daemon.connect(self.plcobj, "PLCObject") print(_("Pyro port :"), self.port) @@ -458,10 +473,7 @@ sys.stdout.flush() - self.daemon.requestLoop() - self.daemon.sock.close() - - def Stop(self): + def _stop(self): if self.plcobj is not None: self.plcobj.StopPLC() if self.servicepublisher is not None: @@ -489,7 +501,6 @@ statuschange = [] if havetwisted: - if havewx: reactor.registerWxApp(app) @@ -528,14 +539,21 @@ statuschange, pyruntimevars=pyruntimevars) -# Exception hooks s +# Exception hooks + + +def LogMessageAndException(msg, exp=None): + if exp is None: + exp = sys.exc_info() + if pyroserver.plcobj is not None: + pyroserver.plcobj.LogMessage(0, msg + '\n'.join(traceback.format_exception(*exp))) + else: + print(msg) + traceback.print_exception(*exp) def LogException(*exp): - if pyroserver.plcobj is not None: - pyroserver.plcobj.LogMessage(0, '\n'.join(traceback.format_exception(*exp))) - else: - traceback.print_exception(*exp) + LogMessageAndException("", exp) sys.excepthook = LogException @@ -570,6 +588,13 @@ webport = None NS.WorkingDir = WorkingDir + # Find pre-existing project WAMP config file + _wampconf = os.path.join(WorkingDir, "wampconf.json") + + # If project's WAMP config file exits, override default (-c) + if os.path.exists(_wampconf): + wampconf = _wampconf + if wampconf is not None: try: import runtime.WampClient as WC # pylint: disable=ungrouped-imports @@ -578,10 +603,9 @@ wampconf = None # Load extensions -for extfilename in extensions: - extension_folder = os.path.split(os.path.realpath(extfilename))[0] +for extention_file, extension_folder in extensions: sys.path.append(extension_folder) - execfile(extfilename, locals()) + execfile(os.path.join(extension_folder, extention_file), locals()) if havetwisted: if webport is not None: @@ -589,16 +613,23 @@ website = NS.RegisterWebsite(webport) pyruntimevars["website"] = website statuschange.append(NS.website_statuslistener_factory(website)) - except Exception, e: - print(_("Nevow Web service failed. "), e) + except Exception: + LogMessageAndException(_("Nevow Web service failed. ")) if wampconf is not None: try: - WC.RegisterWampClient(wampconf) - pyruntimevars["wampsession"] = WC.GetSession - WC.SetServer(pyroserver) - except Exception, e: - print(_("WAMP client startup failed. "), e) + _wampconf = WC.LoadWampClientConf(wampconf) + if _wampconf: + if _wampconf["url"]: # TODO : test more ? + WC.RegisterWampClient(wampconf, wampsecret) + pyruntimevars["wampsession"] = WC.GetSession + WC.SetServer(pyroserver) + else: + raise Exception(_("WAMP config is incomplete.")) + else: + raise Exception(_("WAMP config is missing.")) + except Exception: + LogMessageAndException(_("WAMP client startup failed. ")) if havetwisted or havewx: @@ -612,7 +643,7 @@ else: try: pyroserver.Loop() - except KeyboardInterrupt, e: + except KeyboardInterrupt: pass pyroserver.Quit() sys.exit(0) diff -r 38e912c8bd31 -r cc7a46953471 ConfigTreeNode.py --- a/ConfigTreeNode.py Mon Feb 19 19:36:43 2018 +0300 +++ b/ConfigTreeNode.py Fri Mar 02 17:01:25 2018 +0100 @@ -410,15 +410,16 @@ res = "%s_%d" % (BaseDesiredName, suffix) suffix += 1 - # Get old path - oldname = self.CTNPath() # Check previous confnode existance dontexist = self.BaseParams.getName() == "__unnamed__" + if not dontexist: + # Get old path + oldpath = self.CTNPath() # Set the new name self.BaseParams.setName(res) # Rename confnode dir if exist if not dontexist: - shutil.move(oldname, self.CTNPath()) + shutil.move(oldpath, self.CTNPath()) # warn user he has two left hands if DesiredName != res: msg = _("A child named \"{a1}\" already exists -> \"{a2}\"\n").format(a1=DesiredName, a2=res) diff -r 38e912c8bd31 -r cc7a46953471 IDEFrame.py --- a/IDEFrame.py Mon Feb 19 19:36:43 2018 +0300 +++ b/IDEFrame.py Fri Mar 02 17:01:25 2018 +0100 @@ -44,6 +44,7 @@ from controls.DebugVariablePanel import DebugVariablePanel from dialogs import ProjectDialog, PouDialog, PouTransitionDialog, PouActionDialog, FindInPouDialog, SearchInProjectDialog from util.BitmapLibrary import GetBitmap +from plcopen.types_enums import * # Define PLCOpenEditor controls id [ @@ -824,7 +825,7 @@ return notebook.GetPageIndex(page_ref) elif page_infos[0] == "editor": tagname = page_infos[1] - page_ref = self.EditProjectElement(self.Controler.GetElementType(tagname), tagname) + page_ref = self.EditProjectElement(GetElementType(tagname), tagname) if page_ref is not None: page_ref.RefreshView() return notebook.GetPageIndex(page_ref) @@ -1646,8 +1647,8 @@ abort = True if not abort: self.Controler.ChangeDataTypeName(old_name, new_name) - self.RefreshEditorNames(self.Controler.ComputeDataTypeName(old_name), - self.Controler.ComputeDataTypeName(new_name)) + self.RefreshEditorNames(ComputeDataTypeName(old_name), + ComputeDataTypeName(new_name)) self.RefreshPageTitles() elif item_infos["type"] == ITEM_POU: if new_name.upper() in [name.upper() for name in self.Controler.GetProjectPouNames() if name != old_name]: @@ -1660,8 +1661,8 @@ messageDialog.Destroy() if not abort: self.Controler.ChangePouName(old_name, new_name) - self.RefreshEditorNames(self.Controler.ComputePouName(old_name), - self.Controler.ComputePouName(new_name)) + self.RefreshEditorNames(ComputePouName(old_name), + ComputePouName(new_name)) self.RefreshLibraryPanel() self.RefreshPageTitles() elif item_infos["type"] == ITEM_TRANSITION: @@ -1674,8 +1675,8 @@ else: words = item_infos["tagname"].split("::") self.Controler.ChangePouTransitionName(words[1], old_name, new_name) - self.RefreshEditorNames(self.Controler.ComputePouTransitionName(words[1], old_name), - self.Controler.ComputePouTransitionName(words[1], new_name)) + self.RefreshEditorNames(ComputePouTransitionName(words[1], old_name), + ComputePouTransitionName(words[1], new_name)) self.RefreshPageTitles() elif item_infos["type"] == ITEM_ACTION: pou_item = self.ProjectTree.GetItemParent(event.GetItem()) @@ -1687,8 +1688,8 @@ else: words = item_infos["tagname"].split("::") self.Controler.ChangePouActionName(words[1], old_name, new_name) - self.RefreshEditorNames(self.Controler.ComputePouActionName(words[1], old_name), - self.Controler.ComputePouActionName(words[1], new_name)) + self.RefreshEditorNames(ComputePouActionName(words[1], old_name), + ComputePouActionName(words[1], new_name)) self.RefreshPageTitles() elif item_infos["type"] == ITEM_CONFIGURATION: if new_name.upper() in [name.upper() for name in self.Controler.GetProjectConfigNames() if name != old_name]: @@ -1706,8 +1707,8 @@ messageDialog.Destroy() if not abort: self.Controler.ChangeConfigurationName(old_name, new_name) - self.RefreshEditorNames(self.Controler.ComputeConfigurationName(old_name), - self.Controler.ComputeConfigurationName(new_name)) + self.RefreshEditorNames(ComputeConfigurationName(old_name), + ComputeConfigurationName(new_name)) self.RefreshPageTitles() elif item_infos["type"] == ITEM_RESOURCE: if new_name.upper() in [name.upper() for name in self.Controler.GetProjectConfigNames()]: @@ -1726,8 +1727,8 @@ if not abort: words = item_infos["tagname"].split("::") self.Controler.ChangeConfigurationResourceName(words[1], old_name, new_name) - self.RefreshEditorNames(self.Controler.ComputeConfigurationResourceName(words[1], old_name), - self.Controler.ComputeConfigurationResourceName(words[1], new_name)) + self.RefreshEditorNames(ComputeConfigurationResourceName(words[1], old_name), + ComputeConfigurationResourceName(words[1], new_name)) self.RefreshPageTitles() if message or abort: if message: @@ -2465,7 +2466,7 @@ name = self.ProjectTree.GetItemText(selected) if self.CheckDataTypeIsUsedBeforeDeletion(name): self.Controler.ProjectRemoveDataType(name) - tagname = self.Controler.ComputeDataTypeName(name) + tagname = ComputeDataTypeName(name) idx = self.IsOpened(tagname) if idx is not None: self.TabsOpened.DeletePage(idx) @@ -2482,7 +2483,7 @@ name = self.ProjectTree.GetItemText(selected) if self.CheckPouIsUsedBeforeDeletion(name): self.Controler.ProjectRemovePou(name) - tagname = self.Controler.ComputePouName(name) + tagname = ComputePouName(name) idx = self.IsOpened(tagname) if idx is not None: self.TabsOpened.DeletePage(idx) @@ -2495,7 +2496,7 @@ transition = self.ProjectTree.GetItemText(selected) pou_name = item_infos["tagname"].split("::")[1] self.Controler.ProjectRemovePouTransition(pou_name, transition) - tagname = self.Controler.ComputePouTransitionName(pou_name, transition) + tagname = ComputePouTransitionName(pou_name, transition) idx = self.IsOpened(tagname) if idx is not None: self.TabsOpened.DeletePage(idx) @@ -2508,7 +2509,7 @@ action = self.ProjectTree.GetItemText(selected) pou_name = item_infos["tagname"].split("::")[1] self.Controler.ProjectRemovePouAction(pou_name, action) - tagname = self.Controler.ComputePouActionName(pou_name, action) + tagname = ComputePouActionName(pou_name, action) idx = self.IsOpened(tagname) if idx is not None: self.TabsOpened.DeletePage(idx) @@ -2519,7 +2520,7 @@ if self.ProjectTree.GetPyData(selected)["type"] == ITEM_CONFIGURATION: name = self.ProjectTree.GetItemText(selected) self.Controler.ProjectRemoveConfiguration(name) - tagname = self.Controler.ComputeConfigurationName(name) + tagname = ComputeConfigurationName(name) idx = self.IsOpened(tagname) if idx is not None: self.TabsOpened.DeletePage(idx) @@ -2532,7 +2533,7 @@ resource = self.ProjectTree.GetItemText(selected) config_name = item_infos["tagname"].split("::")[1] self.Controler.ProjectRemoveConfigurationResource(config_name, resource) - tagname = self.Controler.ComputeConfigurationResourceName(config_name, selected) + tagname = ComputeConfigurationResourceName(config_name, selected) idx = self.IsOpened(tagname) if idx is not None: self.TabsOpened.DeletePage(idx) @@ -2549,7 +2550,7 @@ self.RefreshProjectTree() self.ProjectTree.Unselect() else: - self.EditProjectElement(self.Controler.GetElementType(infos[0]), infos[0]) + self.EditProjectElement(GetElementType(infos[0]), infos[0]) selected = self.TabsOpened.GetSelection() if selected != -1: viewer = self.TabsOpened.GetPage(selected) diff -r 38e912c8bd31 -r cc7a46953471 NativeLib.py --- a/NativeLib.py Mon Feb 19 19:36:43 2018 +0300 +++ b/NativeLib.py Fri Mar 02 17:01:25 2018 +0100 @@ -26,9 +26,7 @@ from __future__ import absolute_import import util.paths as paths -from POULibrary import POULibrary +from POULibrary import SimplePOULibraryFactory - -class NativeLibrary(POULibrary): - def GetLibraryPath(self): - return paths.AbsNeighbourFile(__file__, "NativeLib.xml") +NativeLibrary = SimplePOULibraryFactory( + paths.AbsNeighbourFile(__file__, "NativeLib.xml")) diff -r 38e912c8bd31 -r cc7a46953471 PLCControler.py --- a/PLCControler.py Mon Feb 19 19:36:43 2018 +0300 +++ b/PLCControler.py Fri Mar 02 17:01:25 2018 +0100 @@ -31,435 +31,23 @@ import re import datetime from time import localtime -from collections import OrderedDict, namedtuple - -from lxml import etree import util.paths as paths -from util.TranslationCatalogs import NoTranslate from plcopen import * +from plcopen.types_enums import * +from plcopen.InstancesPathCollector import InstancesPathCollector +from plcopen.POUVariablesCollector import POUVariablesCollector +from plcopen.InstanceTagnameCollector import InstanceTagnameCollector +from plcopen.BlockInstanceCollector import BlockInstanceCollector +from plcopen.VariableInfoCollector import VariableInfoCollector from graphics.GraphicCommons import * from PLCGenerator import * duration_model = re.compile("(?:([0-9]{1,2})h)?(?:([0-9]{1,2})m(?!s))?(?:([0-9]{1,2})s)?(?:([0-9]{1,3}(?:\.[0-9]*)?)ms)?") VARIABLE_NAME_SUFFIX_MODEL = re.compile('(\d+)$') -ITEMS_EDITABLE = [ - ITEM_PROJECT, - ITEM_POU, - ITEM_VARIABLE, - ITEM_TRANSITION, - ITEM_ACTION, - ITEM_CONFIGURATION, - ITEM_RESOURCE, - ITEM_DATATYPE -] = range(8) - -ITEMS_UNEDITABLE = [ - ITEM_DATATYPES, - ITEM_FUNCTION, - ITEM_FUNCTIONBLOCK, - ITEM_PROGRAM, - ITEM_TRANSITIONS, - ITEM_ACTIONS, - ITEM_CONFIGURATIONS, - ITEM_RESOURCES, - ITEM_PROPERTIES -] = range(8, 17) - -ITEMS_VARIABLE = [ - ITEM_VAR_LOCAL, - ITEM_VAR_GLOBAL, - ITEM_VAR_EXTERNAL, - ITEM_VAR_TEMP, - ITEM_VAR_INPUT, - ITEM_VAR_OUTPUT, - ITEM_VAR_INOUT -] = range(17, 24) - -VAR_CLASS_INFOS = { - "Local": ("localVars", ITEM_VAR_LOCAL), - "Global": ("globalVars", ITEM_VAR_GLOBAL), - "External": ("externalVars", ITEM_VAR_EXTERNAL), - "Temp": ("tempVars", ITEM_VAR_TEMP), - "Input": ("inputVars", ITEM_VAR_INPUT), - "Output": ("outputVars", ITEM_VAR_OUTPUT), - "InOut": ("inOutVars", ITEM_VAR_INOUT)} - -POU_TYPES = { - "program": ITEM_PROGRAM, - "functionBlock": ITEM_FUNCTIONBLOCK, - "function": ITEM_FUNCTION, -} - -LOCATIONS_ITEMS = [LOCATION_CONFNODE, - LOCATION_MODULE, - LOCATION_GROUP, - LOCATION_VAR_INPUT, - LOCATION_VAR_OUTPUT, - LOCATION_VAR_MEMORY] = range(6) - ScriptDirectory = paths.AbsDir(__file__) - -def GetUneditableNames(): - _ = NoTranslate - return [_("User-defined POUs"), _("Functions"), _("Function Blocks"), - _("Programs"), _("Data Types"), _("Transitions"), _("Actions"), - _("Configurations"), _("Resources"), _("Properties")] - - -UNEDITABLE_NAMES = GetUneditableNames() -[USER_DEFINED_POUS, FUNCTIONS, FUNCTION_BLOCKS, PROGRAMS, - DATA_TYPES, TRANSITIONS, ACTIONS, CONFIGURATIONS, - RESOURCES, PROPERTIES] = UNEDITABLE_NAMES - - -class LibraryResolver(etree.Resolver): - """Helper object for loading library in xslt stylesheets""" - - def __init__(self, controller, debug=False): - self.Controller = controller - self.Debug = debug - - def resolve(self, url, pubid, context): - lib_name = os.path.basename(url) - if lib_name in ["project", "stdlib", "extensions"]: - lib_el = etree.Element(lib_name) - if lib_name == "project": - lib_el.append(deepcopy(self.Controller.GetProject(self.Debug))) - elif lib_name == "stdlib": - for lib in StdBlckLibs.values(): - lib_el.append(deepcopy(lib)) - else: - for ctn in self.Controller.ConfNodeTypes: - lib_el.append(deepcopy(ctn["types"])) - return self.resolve_string(etree.tostring(lib_el), context) - -# ------------------------------------------------------------------------------- -# Helpers functions for translating list of arguments -# from xslt to valid arguments -# ------------------------------------------------------------------------------- - - -def _StringValue(x): - return x - - -def _BoolValue(x): - return x in ["true", "0"] - - -def _translate_args(translations, args): - return [translate(arg[0]) if len(arg) > 0 else None - for translate, arg in - zip(translations, args)] - -# ------------------------------------------------------------------------------- -# Helpers object for generating pou var list -# ------------------------------------------------------------------------------- - - -class _VariableInfos(object): - __slots__ = ["Name", "Class", "Option", "Location", "InitialValue", - "Edit", "Documentation", "Type", "Tree", "Number"] - - def __init__(self, *args): - for attr, value in zip(self.__slots__, args): - setattr(self, attr, value if value is not None else "") - - def copy(self): - return _VariableInfos(*[getattr(self, attr) for attr in self.__slots__]) - - -class VariablesInfosFactory(object): - - def __init__(self, variables): - self.Variables = variables - self.TreeStack = [] - self.Type = None - self.Dimensions = None - - def SetType(self, context, *args): - self.Type = args[0][0] - - def GetType(self): - if len(self.Dimensions) > 0: - return ("array", self.Type, self.Dimensions) - return self.Type - - def GetTree(self): - return (self.TreeStack.pop(-1), self.Dimensions) - - def AddDimension(self, context, *args): - self.Dimensions.append(tuple( - _translate_args([_StringValue] * 2, args))) - - def AddTree(self, context, *args): - self.TreeStack.append([]) - self.Dimensions = [] - - def AddVarToTree(self, context, *args): - var = (args[0][0], self.Type, self.GetTree()) - self.TreeStack[-1].append(var) - - def AddVariable(self, context, *args): - self.Variables.append(_VariableInfos(*( - _translate_args([_StringValue] * 5 + [_BoolValue] + [_StringValue], args) + - [self.GetType(), self.GetTree()]))) - -# ------------------------------------------------------------------------------- -# Helpers object for generating pou variable instance list -# ------------------------------------------------------------------------------- - - -def class_extraction(value): - class_type = { - "configuration": ITEM_CONFIGURATION, - "resource": ITEM_RESOURCE, - "action": ITEM_ACTION, - "transition": ITEM_TRANSITION, - "program": ITEM_PROGRAM}.get(value) - if class_type is not None: - return class_type - - pou_type = POU_TYPES.get(value) - if pou_type is not None: - return pou_type - - var_type = VAR_CLASS_INFOS.get(value) - if var_type is not None: - return var_type[1] - - return None - - -class _VariablesTreeItemInfos(object): - __slots__ = ["name", "var_class", "type", "edit", "debug", "variables"] - - def __init__(self, *args): - for attr, value in zip(self.__slots__, args): - setattr(self, attr, value if value is not None else "") - - def copy(self): - return _VariablesTreeItemInfos(*[getattr(self, attr) for attr in self.__slots__]) - - -class VariablesTreeInfosFactory(object): - - def __init__(self): - self.Root = None - - def GetRoot(self): - return self.Root - - def SetRoot(self, context, *args): - self.Root = _VariablesTreeItemInfos( - *([''] + _translate_args( - [class_extraction, _StringValue] + [_BoolValue] * 2, - args) + [[]])) - - def AddVariable(self, context, *args): - if self.Root is not None: - self.Root.variables.append(_VariablesTreeItemInfos( - *(_translate_args( - [_StringValue, class_extraction, _StringValue] + - [_BoolValue] * 2, args) + [[]]))) - - -class InstancesPathFactory(object): - """Helpers object for generating instances path list""" - def __init__(self, instances): - self.Instances = instances - - def AddInstance(self, context, *args): - self.Instances.append(args[0][0]) - - -class InstanceTagName(object): - """Helpers object for generating instance tagname""" - - def __init__(self, controller): - self.Controller = controller - self.TagName = None - - def GetTagName(self): - return self.TagName - - def ConfigTagName(self, context, *args): - self.TagName = self.Controller.ComputeConfigurationName(args[0][0]) - - def ResourceTagName(self, context, *args): - self.TagName = self.Controller.ComputeConfigurationResourceName(args[0][0], args[1][0]) - - def PouTagName(self, context, *args): - self.TagName = self.Controller.ComputePouName(args[0][0]) - - def ActionTagName(self, context, *args): - self.TagName = self.Controller.ComputePouActionName(args[0][0], args[0][1]) - - def TransitionTagName(self, context, *args): - self.TagName = self.Controller.ComputePouTransitionName(args[0][0], args[0][1]) - - -# ------------------------------------------------------------------------------- -# Helpers object for generating pou block instances list -# ------------------------------------------------------------------------------- - - -_Point = namedtuple("Point", ["x", "y"]) - -_BlockInstanceInfos = namedtuple( - "BlockInstanceInfos", - ["type", "id", "x", "y", "width", "height", "specific_values", "inputs", "outputs"]) - -_BlockSpecificValues = ( - namedtuple("BlockSpecificValues", - ["name", "execution_order"]), - [_StringValue, int]) -_VariableSpecificValues = ( - namedtuple("VariableSpecificValues", - ["name", "value_type", "execution_order"]), - [_StringValue, _StringValue, int]) -_ConnectionSpecificValues = ( - namedtuple("ConnectionSpecificValues", ["name"]), - [_StringValue]) - -_PowerRailSpecificValues = ( - namedtuple("PowerRailSpecificValues", ["connectors"]), - [int]) - -_LDElementSpecificValues = ( - namedtuple("LDElementSpecificValues", - ["name", "negated", "edge", "storage", "execution_order"]), - [_StringValue, _BoolValue, _StringValue, _StringValue, int]) - -_DivergenceSpecificValues = ( - namedtuple("DivergenceSpecificValues", ["connectors"]), - [int]) - -_SpecificValuesTuples = { - "comment": ( - namedtuple("CommentSpecificValues", ["content"]), - [_StringValue]), - "input": _VariableSpecificValues, - "output": _VariableSpecificValues, - "inout": _VariableSpecificValues, - "connector": _ConnectionSpecificValues, - "continuation": _ConnectionSpecificValues, - "leftPowerRail": _PowerRailSpecificValues, - "rightPowerRail": _PowerRailSpecificValues, - "contact": _LDElementSpecificValues, - "coil": _LDElementSpecificValues, - "step": ( - namedtuple("StepSpecificValues", ["name", "initial", "action"]), - [_StringValue, _BoolValue, lambda x: x]), - "transition": ( - namedtuple("TransitionSpecificValues", - ["priority", "condition_type", "condition", "connection"]), - [int, _StringValue, _StringValue, lambda x: x]), - "selectionDivergence": _DivergenceSpecificValues, - "selectionConvergence": _DivergenceSpecificValues, - "simultaneousDivergence": _DivergenceSpecificValues, - "simultaneousConvergence": _DivergenceSpecificValues, - "jump": ( - namedtuple("JumpSpecificValues", ["target"]), - [_StringValue]), - "actionBlock": ( - namedtuple("ActionBlockSpecificValues", ["actions"]), - [lambda x: x]), -} - -_InstanceConnectionInfos = namedtuple( - "InstanceConnectionInfos", - ["name", "negated", "edge", "position", "links"]) - -_ConnectionLinkInfos = namedtuple( - "ConnectionLinkInfos", - ["refLocalId", "formalParameter", "points"]) - - -class _ActionInfos(object): - __slots__ = ["qualifier", "type", "value", "duration", "indicator"] - - def __init__(self, *args): - for attr, value in zip(self.__slots__, args): - setattr(self, attr, value if value is not None else "") - - def copy(self): - return _ActionInfos(*[getattr(self, attr) for attr in self.__slots__]) - - -class BlockInstanceFactory(object): - - def __init__(self, block_instances): - self.BlockInstances = block_instances - self.CurrentInstance = None - self.SpecificValues = None - self.CurrentConnection = None - self.CurrentLink = None - - def SetSpecificValues(self, context, *args): - self.SpecificValues = list(args) - self.CurrentInstance = None - self.CurrentConnection = None - self.CurrentLink = None - - def AddBlockInstance(self, context, *args): - specific_values_tuple, specific_values_translation = \ - _SpecificValuesTuples.get(args[0][0], _BlockSpecificValues) - - if args[0][0] == "step" and len(self.SpecificValues) < 3 or \ - args[0][0] == "transition" and len(self.SpecificValues) < 4: - self.SpecificValues.append([None]) - elif args[0][0] == "actionBlock" and len(self.SpecificValues) < 1: - self.SpecificValues.append([[]]) - specific_values = specific_values_tuple(*_translate_args( - specific_values_translation, self.SpecificValues)) - self.SpecificValues = None - - self.CurrentInstance = _BlockInstanceInfos( - *(_translate_args([_StringValue, int] + [float] * 4, args) + - [specific_values, [], []])) - - self.BlockInstances[self.CurrentInstance.id] = self.CurrentInstance - - def AddInstanceConnection(self, context, *args): - connection_args = _translate_args( - [_StringValue] * 2 + [_BoolValue, _StringValue] + [float] * 2, args) - - self.CurrentConnection = _InstanceConnectionInfos( - *(connection_args[1:4] + [ - _Point(*connection_args[4:6]), []])) - - if self.CurrentInstance is not None: - if connection_args[0] == "input": - self.CurrentInstance.inputs.append(self.CurrentConnection) - else: - self.CurrentInstance.outputs.append(self.CurrentConnection) - else: - self.SpecificValues.append([self.CurrentConnection]) - - def AddConnectionLink(self, context, *args): - self.CurrentLink = _ConnectionLinkInfos( - *(_translate_args([int, _StringValue], args) + [[]])) - self.CurrentConnection.links.append(self.CurrentLink) - - def AddLinkPoint(self, context, *args): - self.CurrentLink.points.append(_Point( - *_translate_args([float] * 2, args))) - - def AddAction(self, context, *args): - if len(self.SpecificValues) == 0: - self.SpecificValues.append([[]]) - translated_args = _translate_args([_StringValue] * 5, args) - self.SpecificValues[0][0].append(_ActionInfos(*translated_args)) - - -pou_block_instances_xslt = etree.parse( - os.path.join(ScriptDirectory, "plcopen", "pou_block_instances.xslt")) - - # Length of the buffer UNDO_BUFFER_LENGTH = 20 @@ -557,6 +145,11 @@ def __init__(self): self.LastNewIndex = 0 self.Reset() + self.InstancesPathCollector = InstancesPathCollector(self) + self.POUVariablesCollector = POUVariablesCollector(self) + self.InstanceTagnameCollector = InstanceTagnameCollector(self) + self.BlockInstanceCollector = BlockInstanceCollector(self) + self.VariableInfoCollector = VariableInfoCollector(self) # Reset PLCControler internal variables def Reset(self): @@ -708,7 +301,7 @@ datatypes["values"].append({ "name": datatype.getname(), "type": ITEM_DATATYPE, - "tagname": self.ComputeDataTypeName(datatype.getname()), + "tagname": ComputeDataTypeName(datatype.getname()), "values": []}) pou_types = { "function": { @@ -730,7 +323,7 @@ for pou in project.getpous(): pou_type = pou.getpouType() pou_infos = {"name": pou.getname(), "type": ITEM_POU, - "tagname": self.ComputePouName(pou.getname())} + "tagname": ComputePouName(pou.getname())} pou_values = [] if pou.getbodyType() == "SFC": transitions = [] @@ -738,7 +331,7 @@ transitions.append({ "name": transition.getname(), "type": ITEM_TRANSITION, - "tagname": self.ComputePouTransitionName(pou.getname(), transition.getname()), + "tagname": ComputePouTransitionName(pou.getname(), transition.getname()), "values": []}) pou_values.append({"name": TRANSITIONS, "type": ITEM_TRANSITIONS, "values": transitions}) actions = [] @@ -746,7 +339,7 @@ actions.append({ "name": action.getname(), "type": ITEM_ACTION, - "tagname": self.ComputePouActionName(pou.getname(), action.getname()), + "tagname": ComputePouActionName(pou.getname(), action.getname()), "values": []}) pou_values.append({"name": ACTIONS, "type": ITEM_ACTIONS, "values": actions}) if pou_type in pou_types: @@ -758,7 +351,7 @@ config_infos = { "name": config_name, "type": ITEM_CONFIGURATION, - "tagname": self.ComputeConfigurationName(config.getname()), + "tagname": ComputeConfigurationName(config.getname()), "values": []} resources = {"name": RESOURCES, "type": ITEM_RESOURCES, "values": []} for resource in config.getresource(): @@ -766,7 +359,7 @@ resource_infos = { "name": resource_name, "type": ITEM_RESOURCE, - "tagname": self.ComputeConfigurationResourceName(config.getname(), resource.getname()), + "tagname": ComputeConfigurationResourceName(config.getname(), resource.getname()), "values": []} resources["values"].append(resource_infos) config_infos["values"] = [resources] @@ -779,18 +372,6 @@ def GetPouVariables(self, tagname, debug=False): project = self.GetProject(debug) if project is not None: - factory = VariablesTreeInfosFactory() - - parser = etree.XMLParser() - parser.resolvers.add(LibraryResolver(self, debug)) - - pou_variable_xslt_tree = etree.XSLT( - etree.parse( - os.path.join(ScriptDirectory, "plcopen", "pou_variables.xslt"), - parser), - extensions={("pou_vars_ns", name): getattr(factory, name) - for name in ["SetRoot", "AddVariable"]}) - obj = None words = tagname.split("::") if words[0] == "P": @@ -798,31 +379,12 @@ elif words[0] != "D": obj = self.GetEditedElement(tagname, debug) if obj is not None: - pou_variable_xslt_tree(obj) - return factory.GetRoot() + return self.POUVariablesCollector.Collect(obj, debug) return None def GetInstanceList(self, root, name, debug=False): - instances = [] - project = self.GetProject(debug) - if project is not None: - factory = InstancesPathFactory(instances) - - parser = etree.XMLParser() - parser.resolvers.add(LibraryResolver(self, debug)) - - instances_path_xslt_tree = etree.XSLT( - etree.parse( - os.path.join(ScriptDirectory, "plcopen", "instances_path.xslt"), - parser), - extensions={ - ("instances_ns", "AddInstance"): factory.AddInstance}) - - instances_path_xslt_tree( - root, instance_type=etree.XSLT.strparam(name)) - - return instances + return self.InstancesPathCollector.Collect(root, name, debug) def SearchPouInstances(self, tagname, debug=False): project = self.GetProject(debug) @@ -837,31 +399,15 @@ elif words[0] in ['T', 'A']: return ["%s.%s" % (instance, words[2]) for instance in self.SearchPouInstances( - self.ComputePouName(words[1]), debug)] + ComputePouName(words[1]), debug)] return [] def GetPouInstanceTagName(self, instance_path, debug=False): project = self.GetProject(debug) - factory = InstanceTagName(self) - - parser = etree.XMLParser() - parser.resolvers.add(LibraryResolver(self, debug)) - - instance_tagname_xslt_tree = etree.XSLT( - etree.parse( - os.path.join(ScriptDirectory, "plcopen", "instance_tagname.xslt"), - parser), - extensions={("instance_tagname_ns", name): getattr(factory, name) - for name in ["ConfigTagName", - "ResourceTagName", - "PouTagName", - "ActionTagName", - "TransitionTagName"]}) - - instance_tagname_xslt_tree( - project, instance_path=etree.XSLT.strparam(instance_path)) - - return factory.GetTagName() + if project is not None: + return self.InstanceTagnameCollector.Collect(project, + debug, + instance_path) def GetInstanceInfos(self, instance_path, debug=False): tagname = self.GetPouInstanceTagName(instance_path) @@ -960,7 +506,7 @@ # Add the datatype to project self.Project.appenddataType(datatype_name) self.BufferProject() - return self.ComputeDataTypeName(datatype_name) + return ComputeDataTypeName(datatype_name) return None # Remove a Data Type from project @@ -977,7 +523,7 @@ if pou_type == "function": self.SetPouInterfaceReturnType(pou_name, "BOOL") self.BufferProject() - return self.ComputePouName(pou_name) + return ComputePouName(pou_name) return None def ProjectChangePouType(self, name, pou_type): @@ -1035,7 +581,7 @@ self.Project.insertpou(0, new_pou) self.BufferProject() - return self.ComputePouName(new_name), + return ComputePouName(new_name), # Remove a Pou from project def ProjectRemovePou(self, pou_name): @@ -1059,7 +605,7 @@ config_name = self.GenerateNewName(None, None, "configuration%d") self.Project.addconfiguration(config_name) self.BufferProject() - return self.ComputeConfigurationName(config_name) + return ComputeConfigurationName(config_name) return None # Remove a configuration from project @@ -1075,7 +621,7 @@ resource_name = self.GenerateNewName(None, None, "resource%d") self.Project.addconfigurationResource(config_name, resource_name) self.BufferProject() - return self.ComputeConfigurationResourceName(config_name, resource_name) + return ComputeConfigurationResourceName(config_name, resource_name) return None # Remove a resource from a configuration of the project @@ -1091,7 +637,7 @@ if pou is not None: pou.addtransition(transition_name, transition_type) self.BufferProject() - return self.ComputePouTransitionName(pou_name, transition_name) + return ComputePouTransitionName(pou_name, transition_name) return None # Remove a Transition from a Project Pou @@ -1110,7 +656,7 @@ if pou is not None: pou.addaction(action_name, action_type) self.BufferProject() - return self.ComputePouActionName(pou_name, action_name) + return ComputePouActionName(pou_name, action_name) return None # Remove an Action from a Project Pou @@ -1381,21 +927,8 @@ def GetVariableDictionary(self, object_with_vars, tree=False, debug=False): variables = [] - factory = VariablesInfosFactory(variables) - - parser = etree.XMLParser() - parser.resolvers.add(LibraryResolver(self, debug)) - - variables_infos_xslt_tree = etree.XSLT( - etree.parse( - os.path.join(ScriptDirectory, "plcopen", "variables_infos.xslt"), - parser), - extensions={("var_infos_ns", name): getattr(factory, name) - for name in ["SetType", "AddDimension", "AddTree", - "AddVarToTree", "AddVariable"]}) - variables_infos_xslt_tree( - object_with_vars, tree=etree.XSLT.strparam(str(tree))) - + self.VariableInfoCollector.Collect(object_with_vars, + debug, variables, tree) return variables # Add a global var to configuration to configuration @@ -1548,20 +1081,8 @@ # Return the return type if there is one return_type = pou.interface.getreturnType() if return_type is not None: - factory = VariablesInfosFactory([]) - - parser = etree.XMLParser() - parser.resolvers.add(LibraryResolver(self)) - - return_type_infos_xslt_tree = etree.XSLT( - etree.parse( - os.path.join(ScriptDirectory, "plcopen", "variables_infos.xslt"), - parser), - extensions={("var_infos_ns", name): getattr(factory, name) - for name in ["SetType", "AddDimension", - "AddTree", "AddVarToTree"]}) - return_type_infos_xslt_tree( - return_type, tree=etree.XSLT.strparam(str(tree))) + factory = self.VariableInfoCollector.Collect(return_type, + debug, [], tree) if tree: return [factory.GetType(), factory.GetTree()] return factory.GetType() @@ -1942,45 +1463,6 @@ return values # ------------------------------------------------------------------------------- - # Project Element tag name computation functions - # ------------------------------------------------------------------------------- - - # Compute a data type name - def ComputeDataTypeName(self, datatype): - return "D::%s" % datatype - - # Compute a pou name - def ComputePouName(self, pou): - return "P::%s" % pou - - # Compute a pou transition name - def ComputePouTransitionName(self, pou, transition): - return "T::%s::%s" % (pou, transition) - - # Compute a pou action name - def ComputePouActionName(self, pou, action): - return "A::%s::%s" % (pou, action) - - # Compute a pou name - def ComputeConfigurationName(self, config): - return "C::%s" % config - - # Compute a pou name - def ComputeConfigurationResourceName(self, config, resource): - return "R::%s::%s" % (config, resource) - - def GetElementType(self, tagname): - words = tagname.split("::") - return { - "D": ITEM_DATATYPE, - "P": ITEM_POU, - "T": ITEM_TRANSITION, - "A": ITEM_ACTION, - "C": ITEM_CONFIGURATION, - "R": ITEM_RESOURCE - }[words[0]] - - # ------------------------------------------------------------------------------- # Project opened Data types management functions # ------------------------------------------------------------------------------- @@ -2487,21 +1969,10 @@ return new_id, connections def GetEditedElementInstancesInfos(self, tagname, debug=False): - element_instances = OrderedDict() element = self.GetEditedElement(tagname, debug) if element is not None: - factory = BlockInstanceFactory(element_instances) - - pou_block_instances_xslt_tree = etree.XSLT( - pou_block_instances_xslt, - extensions={ - ("pou_block_instances_ns", name): getattr(factory, name) - for name in ["AddBlockInstance", "SetSpecificValues", - "AddInstanceConnection", "AddConnectionLink", - "AddLinkPoint", "AddAction"]}) - - pou_block_instances_xslt_tree(element) - return element_instances + return self.BlockInstanceCollector.Collect(element, debug) + return {} def ClearEditedElementExecutionOrder(self, tagname): element = self.GetEditedElement(tagname) diff -r 38e912c8bd31 -r cc7a46953471 PLCGenerator.py --- a/PLCGenerator.py Mon Feb 19 19:36:43 2018 +0300 +++ b/PLCGenerator.py Fri Mar 02 17:01:25 2018 +0100 @@ -28,6 +28,7 @@ import re from plcopen import PLCOpenParser from plcopen.structures import * +from plcopen.types_enums import * # Dictionary associating PLCOpen variable categories to the corresponding @@ -134,7 +135,7 @@ # Getting datatype model from project datatype = self.Project.getdataType(datatype_name) - tagname = self.Controler.ComputeDataTypeName(datatype.getname()) + tagname = ComputeDataTypeName(datatype.getname()) datatype_def = [(" ", ()), (datatype.getname(), (tagname, "name")), (" : ", ())] @@ -268,7 +269,7 @@ # Generate a configuration from its model def GenerateConfiguration(self, configuration): - tagname = self.Controler.ComputeConfigurationName(configuration.getname()) + tagname = ComputeConfigurationName(configuration.getname()) config = [("\nCONFIGURATION ", ()), (configuration.getname(), (tagname, "name")), ("\n", ())] @@ -342,7 +343,7 @@ # Generate a resource from its model def GenerateResource(self, resource, config_name): - tagname = self.Controler.ComputeConfigurationResourceName(config_name, resource.getname()) + tagname = ComputeConfigurationResourceName(config_name, resource.getname()) resrce = [("\n RESOURCE ", ()), (resource.getname(), (tagname, "name")), (" ON PLC\n", ())] @@ -518,7 +519,7 @@ self.ParentGenerator = parent self.Name = name self.Type = type - self.TagName = self.ParentGenerator.Controler.ComputePouName(name) + self.TagName = ComputePouName(name) self.CurrentIndent = " " self.ReturnType = None self.Interface = [] @@ -578,7 +579,7 @@ current_type = var_type break else: - tagname = self.ParentGenerator.Controler.ComputeDataTypeName(current_type) + tagname = ComputeDataTypeName(current_type) infos = self.ParentGenerator.Controler.GetDataTypeInfos(tagname) if infos is not None and infos["type"] == "Structure": name = parts.pop(0) @@ -841,10 +842,10 @@ if body_type == "SFC": previous_tagname = self.TagName for action in pou.getactionList(): - self.TagName = self.ParentGenerator.Controler.ComputePouActionName(self.Name, action.getname()) + self.TagName = ComputePouActionName(self.Name, action.getname()) self.ComputeConnectionTypes(action) for transition in pou.gettransitionList(): - self.TagName = self.ParentGenerator.Controler.ComputePouTransitionName(self.Name, transition.getname()) + self.TagName = ComputePouTransitionName(self.Name, transition.getname()) self.ComputeConnectionTypes(transition) self.TagName = previous_tagname @@ -1497,7 +1498,7 @@ actionContent = pou.getaction(action_name) if actionContent is not None: previous_tagname = self.TagName - self.TagName = self.ParentGenerator.Controler.ComputePouActionName(self.Name, action_name) + self.TagName = ComputePouActionName(self.Name, action_name) self.ComputeProgram(actionContent) self.SFCNetworks["Actions"][action_name] = (self.Program, (self.TagName, "name")) self.Program = [] @@ -1540,7 +1541,7 @@ transitionType = transitionContent.getbodyType() transitionBody = transitionContent.getbody() previous_tagname = self.TagName - self.TagName = self.ParentGenerator.Controler.ComputePouTransitionName(self.Name, transitionValues["value"]) + self.TagName = ComputePouTransitionName(self.Name, transitionValues["value"]) if transitionType == "IL": transition_infos["content"] = [(":\n", ()), (ReIndentText(transitionBody.getcontent().getanyText(), len(self.CurrentIndent)), (self.TagName, "body", len(self.CurrentIndent)))] diff -r 38e912c8bd31 -r cc7a46953471 POULibrary.py --- a/POULibrary.py Mon Feb 19 19:36:43 2018 +0300 +++ b/POULibrary.py Fri Mar 02 17:01:25 2018 +0100 @@ -58,3 +58,10 @@ def Generate_C(self, buildpath, varlist, IECCFLAGS): # Pure python or IEC libs doesn't produce C code return ((""), [], False), "" + + +def SimplePOULibraryFactory(path): + class SimplePOULibrary(POULibrary): + def GetLibraryPath(self): + return path + return SimplePOULibrary diff -r 38e912c8bd31 -r cc7a46953471 ProjectController.py --- a/ProjectController.py Mon Feb 19 19:36:43 2018 +0300 +++ b/ProjectController.py Fri Mar 02 17:01:25 2018 +0100 @@ -58,16 +58,15 @@ from dialogs import DiscoveryDialog from PLCControler import PLCControler from plcopen.structures import IEC_KEYWORDS +from plcopen.types_enums import ComputeConfigurationResourceName, ITEM_CONFNODE import targets -from targets.typemapping import DebugTypesSize, UnpackDebugBuffer +from runtime.typemapping import DebugTypesSize, UnpackDebugBuffer from ConfigTreeNode import ConfigTreeNode, XSDSchemaErrorMessage base_folder = paths.AbsParentDir(__file__) MATIEC_ERROR_MODEL = re.compile(".*\.st:(\d+)-(\d+)\.\.(\d+)-(\d+): (?:error)|(?:warning) : (.*)$") -ITEM_CONFNODE = 25 - def ExtractChildrenTypesFromCatalog(catalog): children_types = [] @@ -420,7 +419,7 @@ config = self.Project.getconfiguration(self.GetProjectMainConfigurationName()) resource = config.getresource()[0].getname() config = config.getname() - resource_tagname = self.ComputeConfigurationResourceName(config, resource) + resource_tagname = ComputeConfigurationResourceName(config, resource) def_task = [ {'Priority': '0', 'Single': '', 'Interval': 'T#20ms', 'Name': 'task0', 'Triggering': 'Cyclic'}] def_instance = [ @@ -1808,6 +1807,16 @@ self._SetConnector(None) def _Transfer(self): + if self.IsPLCStarted(): + dialog = wx.MessageDialog( + self.AppFrame, + _("Cannot transfer while PLC is running. Stop it now?"), + style=wx.YES_NO | wx.CENTRE) + if dialog.ShowModal() == wx.ID_YES: + self._Stop() + else: + return + # Get the last build PLC's MD5 = self.GetLastBuildMD5() diff -r 38e912c8bd31 -r cc7a46953471 canfestival/canfestival.py --- a/canfestival/canfestival.py Mon Feb 19 19:36:43 2018 +0300 +++ b/canfestival/canfestival.py Fri Mar 02 17:01:25 2018 +0100 @@ -28,8 +28,8 @@ import sys import shutil import wx -from gnosis.xml.pickle import * -from gnosis.xml.pickle.util import setParanoia +from gnosis.xml.pickle import * # pylint: disable=import-error +from gnosis.xml.pickle.util import setParanoia # pylint: disable=import-error import util.paths as paths from util.TranslationCatalogs import AddCatalog @@ -50,7 +50,7 @@ from nodemanager import NodeManager import gen_cfile import eds_utils -import canfestival_config as local_canfestival_config +import canfestival_config as local_canfestival_config # pylint: disable=import-error from commondialogs import CreateNodeDialog from subindextable import IECTypeConversion, SizeConversion @@ -463,7 +463,9 @@ raise Exception(res) file = open(os.path.join(buildpath, "MasterGenerated.od"), "w") - dump(master, file) + # linter disabled here, undefined variable happens + # here because gnosis isn't impored while linting + dump(master, file) # pylint: disable=undefined-variable file.close() return [(Gen_OD_path, local_canfestival_config.getCFLAGS(CanFestivalPath))], "", False diff -r 38e912c8bd31 -r cc7a46953471 canfestival/config_utils.py --- a/canfestival/config_utils.py Mon Feb 19 19:36:43 2018 +0300 +++ b/canfestival/config_utils.py Fri Mar 02 17:01:25 2018 +0100 @@ -697,7 +697,7 @@ return pointers -if __name__ == "__main__": +if __name__ == "__main__": # pylint: disable=all def usage(): print(""" Usage of config_utils.py test : diff -r 38e912c8bd31 -r cc7a46953471 connectors/PYRO/__init__.py --- a/connectors/PYRO/__init__.py Mon Feb 19 19:36:43 2018 +0300 +++ b/connectors/PYRO/__init__.py Fri Mar 02 17:01:25 2018 +0100 @@ -76,7 +76,7 @@ def _settimeout(self, timeout): self.timeout = timeout - from M2Crypto.SSL import Connection + from M2Crypto.SSL import Connection # pylint: disable=import-error Connection.timeout = None Connection.gettimeout = _gettimeout Connection.settimeout = _settimeout diff -r 38e912c8bd31 -r cc7a46953471 connectors/WAMP/__init__.py --- a/connectors/WAMP/__init__.py Mon Feb 19 19:36:43 2018 +0300 +++ b/connectors/WAMP/__init__.py Fri Mar 02 17:01:25 2018 +0100 @@ -27,7 +27,6 @@ from __future__ import print_function import sys import traceback -import atexit from threading import Thread, Event from twisted.internet import reactor, threads @@ -83,7 +82,7 @@ # create a WAMP application session factory component_config = types.ComponentConfig( - realm=realm, + realm=unicode(realm), extra={"ID": ID}) session_factory = wamp.ApplicationSessionFactory( config=component_config) @@ -93,9 +92,7 @@ transport_factory = WampWebSocketClientFactory( session_factory, url=url, - serializers=[MsgPackSerializer()], - debug=False, - debug_wamp=False) + serializers=[MsgPackSerializer()]) # start the client from a Twisted endpoint conn = connectWS(transport_factory) @@ -111,7 +108,7 @@ reactor.run(installSignalHandlers=False) def WampSessionProcMapper(funcname): - wampfuncname = '.'.join((ID, funcname)) + wampfuncname = unicode('.'.join((ID, funcname))) def catcher_func(*args, **kwargs): if _WampSession is not None: diff -r 38e912c8bd31 -r cc7a46953471 controls/DebugVariablePanel/DebugVariableGraphicViewer.py --- a/controls/DebugVariablePanel/DebugVariableGraphicViewer.py Mon Feb 19 19:36:43 2018 +0300 +++ b/controls/DebugVariablePanel/DebugVariableGraphicViewer.py Fri Mar 02 17:01:25 2018 +0100 @@ -26,7 +26,7 @@ from __future__ import absolute_import from types import TupleType from time import time as gettime -from distutils.version import LooseVersion +from cycler import cycler import numpy import wx @@ -42,10 +42,6 @@ from controls.DebugVariablePanel.GraphButton import GraphButton -if LooseVersion(matplotlib.__version__) >= LooseVersion("1.5.0"): - from cycler import cycler - - # Graph variable display type GRAPH_PARALLEL, GRAPH_ORTHOGONAL = range(2) @@ -983,10 +979,7 @@ return AddText def SetAxesColor(self, color): - if LooseVersion(matplotlib.__version__) >= LooseVersion("1.5.0"): - self.Axes.set_prop_cycle(cycler('color', color)) - else: - self.Axes.set_color_cycle(color) + self.Axes.set_prop_cycle(cycler('color', color)) def ResetGraphics(self): """ diff -r 38e912c8bd31 -r cc7a46953471 controls/LogViewer.py --- a/controls/LogViewer.py Mon Feb 19 19:36:43 2018 +0300 +++ b/controls/LogViewer.py Fri Mar 02 17:01:25 2018 +0100 @@ -33,7 +33,7 @@ from controls.CustomToolTip import CustomToolTip, TOOLTIP_WAIT_PERIOD from editors.DebugViewer import DebugViewer, REFRESH_PERIOD -from targets.typemapping import LogLevelsCount, LogLevels +from runtime.loglevels import LogLevelsCount, LogLevels from util.BitmapLibrary import GetBitmap diff -r 38e912c8bd31 -r cc7a46953471 controls/PouInstanceVariablesPanel.py --- a/controls/PouInstanceVariablesPanel.py Mon Feb 19 19:36:43 2018 +0300 +++ b/controls/PouInstanceVariablesPanel.py Fri Mar 02 17:01:25 2018 +0100 @@ -30,13 +30,7 @@ import wx.lib.agw.customtreectrl as CT import wx.lib.buttons -from PLCControler import \ - ITEMS_VARIABLE, \ - ITEM_CONFIGURATION, \ - ITEM_RESOURCE, \ - ITEM_POU, \ - ITEM_TRANSITION, \ - ITEM_ACTION +from plcopen.types_enums import * from util.BitmapLibrary import GetBitmap @@ -220,7 +214,7 @@ if tagname == "Project": config_name = self.Controller.GetProjectMainConfigurationName() if config_name is not None: - tagname = self.Controller.ComputeConfigurationName(config_name) + tagname = ComputeConfigurationName(config_name) if pou_instance is not None: self.PouInstance = pou_instance @@ -316,20 +310,20 @@ def EditButtonCallback(self, infos): var_class = infos.var_class if var_class == ITEM_RESOURCE: - tagname = self.Controller.ComputeConfigurationResourceName( + tagname = ComputeConfigurationResourceName( self.InstanceChoice.GetStringSelection(), infos.name) elif var_class == ITEM_TRANSITION: - tagname = self.Controller.ComputePouTransitionName( + tagname = ComputePouTransitionName( self.PouTagName.split("::")[1], infos.name) elif var_class == ITEM_ACTION: - tagname = self.Controller.ComputePouActionName( + tagname = ComputePouActionName( self.PouTagName.split("::")[1], infos.name) else: var_class = ITEM_POU - tagname = self.Controller.ComputePouName(infos.type) + tagname = ComputePouName(infos.type) self.ParentWindow.EditProjectElement(var_class, tagname) def DebugButtonCallback(self, infos): @@ -346,21 +340,21 @@ self.ParentWindow.OpenDebugViewer( var_class, var_path, - self.Controller.ComputePouTransitionName( + ComputePouTransitionName( self.PouTagName.split("::")[1], infos.name)) elif var_class == ITEM_ACTION: self.ParentWindow.OpenDebugViewer( var_class, var_path, - self.Controller.ComputePouActionName( + ComputePouActionName( self.PouTagName.split("::")[1], infos.name)) else: self.ParentWindow.OpenDebugViewer( var_class, var_path, - self.Controller.ComputePouName(infos.type)) + ComputePouName(infos.type)) def DebugButtonDClickCallback(self, infos): if self.InstanceChoice.GetSelection() != -1: @@ -420,7 +414,7 @@ instance_path = self.InstanceChoice.GetStringSelection() if item_infos.var_class == ITEM_RESOURCE: if instance_path != "": - tagname = self.Controller.ComputeConfigurationResourceName( + tagname = ComputeConfigurationResourceName( instance_path, item_infos.name) else: @@ -428,11 +422,11 @@ else: parent_infos = self.VariablesList.GetPyData(selected_item.GetParent()) if item_infos.var_class == ITEM_ACTION: - tagname = self.Controller.ComputePouActionName(parent_infos.type, item_infos.name) + tagname = ComputePouActionName(parent_infos.type, item_infos.name) elif item_infos.var_class == ITEM_TRANSITION: - tagname = self.Controller.ComputePouTransitionName(parent_infos.type, item_infos.name) + tagname = ComputePouTransitionName(parent_infos.type, item_infos.name) else: - tagname = self.Controller.ComputePouName(item_infos.type) + tagname = ComputePouName(item_infos.type) if tagname is not None: if instance_path != "": item_path = "%s.%s" % (instance_path, item_infos.name) diff -r 38e912c8bd31 -r cc7a46953471 controls/ProjectPropertiesPanel.py --- a/controls/ProjectPropertiesPanel.py Mon Feb 19 19:36:43 2018 +0300 +++ b/controls/ProjectPropertiesPanel.py Fri Mar 02 17:01:25 2018 +0100 @@ -25,6 +25,7 @@ from __future__ import absolute_import import wx +from wx.lib.scrolledpanel import ScrolledPanel from xmlclass.xmlclass import URI_model @@ -76,7 +77,9 @@ # Project Panel elements - self.ProjectPanel = wx.Panel(self, style=wx.TAB_TRAVERSAL) + self.ProjectPanel = ScrolledPanel(self, -1, style=wx.TAB_TRAVERSAL) + self.ProjectPanel.SetAutoLayout(1) + self.ProjectPanel.SetupScrolling() projectpanel_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=5, vgap=15) projectpanel_sizer.AddGrowableCol(1) self.ProjectPanel.SetSizer(projectpanel_sizer) @@ -92,7 +95,9 @@ # Author Panel elements - self.AuthorPanel = wx.Panel(self, style=wx.TAB_TRAVERSAL) + self.AuthorPanel = ScrolledPanel(self, -1, style=wx.TAB_TRAVERSAL) + self.AuthorPanel.SetAutoLayout(1) + self.AuthorPanel.SetupScrolling() authorpanel_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=4, vgap=15) authorpanel_sizer.AddGrowableCol(1) self.AuthorPanel.SetSizer(authorpanel_sizer) @@ -107,7 +112,9 @@ # Graphics Panel elements - self.GraphicsPanel = wx.Panel(self, style=wx.TAB_TRAVERSAL) + self.GraphicsPanel = ScrolledPanel(self, -1, style=wx.TAB_TRAVERSAL) + self.GraphicsPanel.SetAutoLayout(1) + self.GraphicsPanel.SetupScrolling() graphicpanel_sizer = wx.FlexGridSizer(cols=1, hgap=5, rows=4, vgap=5) graphicpanel_sizer.AddGrowableCol(0) graphicpanel_sizer.AddGrowableRow(3) @@ -183,9 +190,13 @@ # Miscellaneous Panel elements - self.MiscellaneousPanel = wx.Panel( - id=-1, parent=self, name='MiscellaneousPanel', pos=wx.Point(0, 0), - size=wx.Size(0, 0), style=wx.TAB_TRAVERSAL) + self.MiscellaneousPanel = ScrolledPanel(id=-1, parent=self, + name='MiscellaneousPanel', + pos=wx.Point(0, 0), + size=wx.Size(0, 0), + style=wx.TAB_TRAVERSAL) + self.MiscellaneousPanel.SetAutoLayout(1) + self.MiscellaneousPanel.SetupScrolling() miscellaneouspanel_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=2, vgap=15) miscellaneouspanel_sizer.AddGrowableCol(1) miscellaneouspanel_sizer.AddGrowableRow(1) diff -r 38e912c8bd31 -r cc7a46953471 controls/SearchResultPanel.py --- a/controls/SearchResultPanel.py Mon Feb 19 19:36:43 2018 +0300 +++ b/controls/SearchResultPanel.py Fri Mar 02 17:01:25 2018 +0100 @@ -32,6 +32,7 @@ from PLCControler import * from util.BitmapLibrary import GetBitmap +from plcopen.types_enums import GetElementType def GenerateName(infos): @@ -196,7 +197,7 @@ words = tagname.split("::") - element_type = self.ParentWindow.Controler.GetElementType(tagname) + element_type = GetElementType(tagname) if element_type == ITEM_POU: element_type = self.ParentWindow.Controler.GetPouType(words[1]) diff -r 38e912c8bd31 -r cc7a46953471 controls/VariablePanel.py --- a/controls/VariablePanel.py Mon Feb 19 19:36:43 2018 +0300 +++ b/controls/VariablePanel.py Fri Mar 02 17:01:25 2018 +0100 @@ -32,6 +32,7 @@ import wx.lib.buttons from plcopen.structures import LOCATIONDATATYPES, TestIdentifier, IEC_KEYWORDS, DefaultType +from plcopen.VariableInfoCollector import _VariableInfos from graphics.GraphicCommons import REFRESH_HIGHLIGHT_PERIOD, ERROR_HIGHLIGHT from dialogs.ArrayTypeDialog import ArrayTypeDialog from controls.CustomGrid import CustomGrid @@ -39,7 +40,6 @@ from controls.LocationCellEditor import LocationCellEditor from util.BitmapLibrary import GetBitmap from util.TranslationCatalogs import NoTranslate -from PLCControler import _VariableInfos # ------------------------------------------------------------------------------- diff -r 38e912c8bd31 -r cc7a46953471 dialogs/ActionBlockDialog.py --- a/dialogs/ActionBlockDialog.py Mon Feb 19 19:36:43 2018 +0300 +++ b/dialogs/ActionBlockDialog.py Fri Mar 02 17:01:25 2018 +0100 @@ -29,7 +29,7 @@ import wx.lib.buttons from controls import CustomGrid, CustomTable -from PLCControler import _ActionInfos +from plcopen.BlockInstanceCollector import _ActionInfos from util.BitmapLibrary import GetBitmap from util.TranslationCatalogs import NoTranslate # ------------------------------------------------------------------------------- diff -r 38e912c8bd31 -r cc7a46953471 editors/ProjectNodeEditor.py --- a/editors/ProjectNodeEditor.py Mon Feb 19 19:36:43 2018 +0300 +++ b/editors/ProjectNodeEditor.py Fri Mar 02 17:01:25 2018 +0100 @@ -28,6 +28,7 @@ from controls import ProjectPropertiesPanel, VariablePanel from editors.ConfTreeNodeEditor import ConfTreeNodeEditor +from plcopen.types_enums import ComputeConfigurationName class ProjectNodeEditor(ConfTreeNodeEditor): @@ -52,7 +53,7 @@ def __init__(self, parent, controler, window): configuration = controler.GetProjectMainConfigurationName() if configuration is not None: - tagname = controler.ComputeConfigurationName(configuration) + tagname = ComputeConfigurationName(configuration) else: tagname = "" diff -r 38e912c8bd31 -r cc7a46953471 editors/TextViewer.py --- a/editors/TextViewer.py Mon Feb 19 19:36:43 2018 +0300 +++ b/editors/TextViewer.py Fri Mar 02 17:01:25 2018 +0100 @@ -424,7 +424,6 @@ def RefreshJumpList(self): if self.TextSyntax == "IL": self.Jumps = [jump.upper() for jump in LABEL_MODEL.findall(self.GetText())] - self.Colourise(0, -1) # Buffer the last model state def RefreshBuffer(self): @@ -848,6 +847,7 @@ def RefreshModel(self): self.RefreshJumpList() + self.Colourise(0, -1) self.Controler.SetEditedElementText(self.TagName, self.GetText()) self.ResetSearchResults() diff -r 38e912c8bd31 -r cc7a46953471 editors/Viewer.py --- a/editors/Viewer.py Mon Feb 19 19:36:43 2018 +0300 +++ b/editors/Viewer.py Fri Mar 02 17:01:25 2018 +0100 @@ -32,9 +32,16 @@ import wx from plcopen.structures import * +from plcopen.types_enums import ComputePouName from PLCControler import ITEM_VAR_LOCAL, ITEM_POU, ITEM_PROGRAM, ITEM_FUNCTIONBLOCK + +from graphics.GraphicCommons import * +from graphics.FBD_Objects import * +from graphics.LD_Objects import * +from graphics.SFC_Objects import * +from graphics.RubberBand import RubberBand from graphics.DebugDataConsumer import DebugDataConsumer -from graphics import * + from dialogs import * from editors.DebugViewer import DebugViewer, REFRESH_PERIOD from editors.EditorPanel import EditorPanel @@ -2278,7 +2285,7 @@ self.ParentWindow.OpenDebugViewer( pou_type, "%s.%s" % (self.GetInstancePath(True), self.SelectedElement.GetName()), - self.Controler.ComputePouName(instance_type)) + ComputePouName(instance_type)) else: iec_path = self.GetElementIECPath(self.SelectedElement) if iec_path is not None: @@ -2298,7 +2305,7 @@ if instance_type in self.Controler.GetProjectPouNames(self.Debug): self.ParentWindow.EditProjectElement( ITEM_POU, - self.Controler.ComputePouName(instance_type)) + ComputePouName(instance_type)) else: self.SelectedElement.OnLeftDClick(event, self.GetLogicalDC(), self.Scaling) elif event.ControlDown() and event.ShiftDown(): diff -r 38e912c8bd31 -r cc7a46953471 features.py --- a/features.py Mon Feb 19 19:36:43 2018 +0300 +++ b/features.py Fri Mar 02 17:01:25 2018 +0100 @@ -29,6 +29,7 @@ catalog = [ ('canfestival', _('CANopen support'), _('Map located variables over CANopen'), 'canfestival.canfestival.RootClass'), + ('modbus', _('Modbus support'), _('Map located variables over Modbus'), 'modbus.modbus.RootClass'), ('c_ext', _('C extension'), _('Add C code accessing located variables synchronously'), 'c_ext.CFile'), ('py_ext', _('Python file'), _('Add Python code executed asynchronously'), 'py_ext.PythonFile'), ('wxglade_hmi', _('WxGlade GUI'), _('Add a simple WxGlade based GUI.'), 'wxglade_hmi.WxGladeHMI'), diff -r 38e912c8bd31 -r cc7a46953471 graphics/__init__.py --- a/graphics/__init__.py Mon Feb 19 19:36:43 2018 +0300 +++ b/graphics/__init__.py Fri Mar 02 17:01:25 2018 +0100 @@ -23,12 +23,3 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Package initialisation - -from __future__ import absolute_import - -from graphics.GraphicCommons import * -from graphics.FBD_Objects import * -from graphics.LD_Objects import * -from graphics.SFC_Objects import * -from graphics.RubberBand import RubberBand -from graphics.DebugDataConsumer import DebugDataConsumer diff -r 38e912c8bd31 -r cc7a46953471 i18n/mki18n.py --- a/i18n/mki18n.py Mon Feb 19 19:36:43 2018 +0300 +++ b/i18n/mki18n.py Fri Mar 02 17:01:25 2018 +0100 @@ -439,7 +439,8 @@ # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # if __name__ == "__main__": - import getopt # command line parsing + # command line parsing + import getopt # pylint: disable=wrong-import-order,wrong-import-position argc = len(sys.argv) if argc == 1: printUsage('Missing argument: specify at least one of -m or -p (or both).') diff -r 38e912c8bd31 -r cc7a46953471 modbus/README --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modbus/README Fri Mar 02 17:01:25 2018 +0100 @@ -0,0 +1,1 @@ +Modbus diff -r 38e912c8bd31 -r cc7a46953471 modbus/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modbus/__init__.py Fri Mar 02 17:01:25 2018 +0100 @@ -0,0 +1,23 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# This file is part of Beremiz, a Integrated Development Environment for +# programming IEC 61131-3 automates supporting plcopen standard and CanFestival. +# +# Copyright (c) 2016 Mario de Sousa (msousa@fe.up.pt) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# This code is made available on the understanding that it will not be +# used in safety-critical situations without a full and competent review. diff -r 38e912c8bd31 -r cc7a46953471 modbus/mb_runtime.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modbus/mb_runtime.c Fri Mar 02 17:01:25 2018 +0100 @@ -0,0 +1,603 @@ +/* File generated by Beremiz (PlugGenerate_C method of Modbus plugin) */ + +/* + * Copyright (c) 2016 Mario de Sousa (msousa@fe.up.pt) + * + * This file is part of the Modbus library for Beremiz and matiec. + * + * This Modbus library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this Modbus library. If not, see . + * + * This code is made available on the understanding that it will not be + * used in safety-critical situations without a full and competent review. + */ + + +#include +#include /* required for memcpy() */ +#include "mb_slave_and_master.h" +#include "MB_%(locstr)s.h" + + +#define MAX_MODBUS_ERROR_CODE 11 +static const char *modbus_error_messages[MAX_MODBUS_ERROR_CODE+1] = { + /* 0 */ "", /* un-used -> no error! */ + /* 1 */ "illegal/unsuported function", + /* 2 */ "illegal data address", + /* 3 */ "illegal data value", + /* 4 */ "slave device failure", + /* 5 */ "acknowledge -> slave intends to reply later", + /* 6 */ "slave device busy", + /* 7 */ "negative acknowledge", + /* 8 */ "memory parity error", + /* 9 */ "", /* undefined by Modbus */ + /* 10*/ "gateway path unavalilable", + /* 11*/ "gateway target device failed to respond" +}; + + +/* Execute a modbus client transaction/request */ +static int __execute_mb_request(int request_id){ + switch (client_requests[request_id].mb_function){ + + case 1: /* read coils */ + return read_output_bits(client_requests[request_id].slave_id, + client_requests[request_id].address, + client_requests[request_id].count, + client_requests[request_id].coms_buffer, + (int) client_requests[request_id].count, + client_nodes[client_requests[request_id].client_node_id].mb_nd, + client_requests[request_id].retries, + &(client_requests[request_id].error_code), + &(client_requests[request_id].resp_timeout), + &(client_requests[request_id].coms_buf_mutex)); + + case 2: /* read discrete inputs */ + return read_input_bits( client_requests[request_id].slave_id, + client_requests[request_id].address, + client_requests[request_id].count, + client_requests[request_id].coms_buffer, + (int) client_requests[request_id].count, + client_nodes[client_requests[request_id].client_node_id].mb_nd, + client_requests[request_id].retries, + &(client_requests[request_id].error_code), + &(client_requests[request_id].resp_timeout), + &(client_requests[request_id].coms_buf_mutex)); + + case 3: /* read holding registers */ + return read_output_words(client_requests[request_id].slave_id, + client_requests[request_id].address, + client_requests[request_id].count, + client_requests[request_id].coms_buffer, + (int) client_requests[request_id].count, + client_nodes[client_requests[request_id].client_node_id].mb_nd, + client_requests[request_id].retries, + &(client_requests[request_id].error_code), + &(client_requests[request_id].resp_timeout), + &(client_requests[request_id].coms_buf_mutex)); + + case 4: /* read input registers */ + return read_input_words(client_requests[request_id].slave_id, + client_requests[request_id].address, + client_requests[request_id].count, + client_requests[request_id].coms_buffer, + (int) client_requests[request_id].count, + client_nodes[client_requests[request_id].client_node_id].mb_nd, + client_requests[request_id].retries, + &(client_requests[request_id].error_code), + &(client_requests[request_id].resp_timeout), + &(client_requests[request_id].coms_buf_mutex)); + + case 5: /* write single coil */ + return write_output_bit(client_requests[request_id].slave_id, + client_requests[request_id].address, + client_requests[request_id].coms_buffer[0], + client_nodes[client_requests[request_id].client_node_id].mb_nd, + client_requests[request_id].retries, + &(client_requests[request_id].error_code), + &(client_requests[request_id].resp_timeout), + &(client_requests[request_id].coms_buf_mutex)); + + case 6: /* write single register */ + return write_output_word(client_requests[request_id].slave_id, + client_requests[request_id].address, + client_requests[request_id].coms_buffer[0], + client_nodes[client_requests[request_id].client_node_id].mb_nd, + client_requests[request_id].retries, + &(client_requests[request_id].error_code), + &(client_requests[request_id].resp_timeout), + &(client_requests[request_id].coms_buf_mutex)); + + case 7: break; /* function not yet supported */ + case 8: break; /* function not yet supported */ + case 9: break; /* function not yet supported */ + case 10: break; /* function not yet supported */ + case 11: break; /* function not yet supported */ + case 12: break; /* function not yet supported */ + case 13: break; /* function not yet supported */ + case 14: break; /* function not yet supported */ + + case 15: /* write multiple coils */ + return write_output_bits(client_requests[request_id].slave_id, + client_requests[request_id].address, + client_requests[request_id].count, + client_requests[request_id].coms_buffer, + client_nodes[client_requests[request_id].client_node_id].mb_nd, + client_requests[request_id].retries, + &(client_requests[request_id].error_code), + &(client_requests[request_id].resp_timeout), + &(client_requests[request_id].coms_buf_mutex)); + + case 16: /* write multiple registers */ + return write_output_words(client_requests[request_id].slave_id, + client_requests[request_id].address, + client_requests[request_id].count, + client_requests[request_id].coms_buffer, + client_nodes[client_requests[request_id].client_node_id].mb_nd, + client_requests[request_id].retries, + &(client_requests[request_id].error_code), + &(client_requests[request_id].resp_timeout), + &(client_requests[request_id].coms_buf_mutex)); + + default: break; /* should never occur, if file generation is correct */ + } + + fprintf(stderr, "Modbus plugin: Modbus function %%d not supported\n", request_id); /* should never occur, if file generation is correct */ + return -1; +} + + + +/* pack bits from unpacked_data to packed_data */ +static inline int __pack_bits(u16 *unpacked_data, u16 start_addr, u16 bit_count, u8 *packed_data) { + u8 bit; + u16 byte, coils_processed; + + if ((0 == bit_count) || (65535-start_addr < bit_count-1)) + return -ERR_ILLEGAL_DATA_ADDRESS; /* ERR_ILLEGAL_DATA_ADDRESS defined in mb_util.h */ + + for( byte = 0, coils_processed = 0; coils_processed < bit_count; byte++) { + packed_data[byte] = 0; + for( bit = 0x01; (bit & 0xFF) && (coils_processed < bit_count); bit <<= 1, coils_processed++ ) { + if(unpacked_data[start_addr + coils_processed]) + packed_data[byte] |= bit; /* set bit */ + else packed_data[byte] &= ~bit; /* reset bit */ + } + } + return 0; +} + + +/* unpack bits from packed_data to unpacked_data */ +static inline int __unpack_bits(u16 *unpacked_data, u16 start_addr, u16 bit_count, u8 *packed_data) { + u8 temp, bit; + u16 byte, coils_processed; + + if ((0 == bit_count) || (65535-start_addr < bit_count-1)) + return -ERR_ILLEGAL_DATA_ADDRESS; /* ERR_ILLEGAL_DATA_ADDRESS defined in mb_util.h */ + + for(byte = 0, coils_processed = 0; coils_processed < bit_count; byte++) { + temp = packed_data[byte] ; + for(bit = 0x01; (bit & 0xff) && (coils_processed < bit_count); bit <<= 1, coils_processed++) { + unpacked_data[start_addr + coils_processed] = (temp & bit)?1:0; + } + } + return 0; +} + + +static int __read_inbits (void *mem_map, u16 start_addr, u16 bit_count, u8 *data_bytes) + {return __pack_bits(((server_mem_t *)mem_map)->ro_bits, start_addr, bit_count, data_bytes);} +static int __read_outbits (void *mem_map, u16 start_addr, u16 bit_count, u8 *data_bytes) + {return __pack_bits(((server_mem_t *)mem_map)->rw_bits, start_addr, bit_count, data_bytes);} +static int __write_outbits (void *mem_map, u16 start_addr, u16 bit_count, u8 *data_bytes) + {return __unpack_bits(((server_mem_t *)mem_map)->rw_bits, start_addr, bit_count, data_bytes); } + + + +static int __read_inwords (void *mem_map, u16 start_addr, u16 word_count, u16 *data_words) { + + if ((start_addr + word_count) > MEM_AREA_SIZE) + return -ERR_ILLEGAL_DATA_ADDRESS; /* ERR_ILLEGAL_DATA_ADDRESS defined in mb_util.h */ + + /* use memcpy() because loop with pointers (u16 *) caused alignment problems */ + memcpy(/* dest */ (void *)data_words, + /* src */ (void *)&(((server_mem_t *)mem_map)->ro_words[start_addr]), + /* size */ word_count * 2); + return 0; +} + + + +static int __read_outwords (void *mem_map, u16 start_addr, u16 word_count, u16 *data_words) { + + if ((start_addr + word_count) > MEM_AREA_SIZE) + return -ERR_ILLEGAL_DATA_ADDRESS; /* ERR_ILLEGAL_DATA_ADDRESS defined in mb_util.h */ + + /* use memcpy() because loop with pointers (u16 *) caused alignment problems */ + memcpy(/* dest */ (void *)data_words, + /* src */ (void *)&(((server_mem_t *)mem_map)->rw_words[start_addr]), + /* size */ word_count * 2); + return 0; +} + + + + +static int __write_outwords(void *mem_map, u16 start_addr, u16 word_count, u16 *data_words) { + + if ((start_addr + word_count) > MEM_AREA_SIZE) + return -ERR_ILLEGAL_DATA_ADDRESS; /* ERR_ILLEGAL_DATA_ADDRESS defined in mb_util.h */ + + /* WARNING: The data returned in the data_words[] array is not guaranteed to be 16 bit aligned. + * It is not therefore safe to cast it to an u16 data type. + * The following code cannot be used. memcpy() is used instead. + */ + /* + for (count = 0; count < word_count ; count++) + ((server_mem_t *)mem_map)->rw_words[count + start_addr] = data_words[count]; + */ + memcpy(/* dest */ (void *)&(((server_mem_t *)mem_map)->rw_words[start_addr]), + /* src */ (void *)data_words, + /* size */ word_count * 2); + return 0; +} + + + + +#include + +static void *__mb_server_thread(void *_server_node) { + server_node_t *server_node = _server_node; + mb_slave_callback_t callbacks = { + &__read_inbits, + &__read_outbits, + &__write_outbits, + &__read_inwords, + &__read_outwords, + &__write_outwords, + (void *)&(server_node->mem_area) + }; + + // Enable thread cancelation. Enabled is default, but set it anyway to be safe. + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); + + // mb_slave_run() should never return! + mb_slave_run(server_node->mb_nd /* nd */, callbacks, server_node->slave_id); + fprintf(stderr, "Modbus plugin: Modbus server for node %%s died unexpectedly!\n", server_node->location); /* should never occur */ + return NULL; +} + + + +static void *__mb_client_thread(void *_index) { + int client_node_id = (char *)_index - (char *)NULL; // Use pointer arithmetic (more portable than cast) + struct timespec next_cycle; + int period_sec = client_nodes[client_node_id].comm_period / 1000; /* comm_period is in ms */ + int period_nsec = (client_nodes[client_node_id].comm_period %%1000)*1000000; /* comm_period is in ms */ + + // Enable thread cancelation. Enabled is default, but set it anyway to be safe. + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); + + // get the current time + clock_gettime(CLOCK_MONOTONIC, &next_cycle); + + // loop the communication with the client + while (1) { + /* + struct timespec cur_time; + clock_gettime(CLOCK_MONOTONIC, &cur_time); + fprintf(stderr, "Modbus client thread - new cycle (%%ld:%%ld)!\n", cur_time.tv_sec, cur_time.tv_nsec); + */ + int req; + for (req=0; req < NUMBER_OF_CLIENT_REQTS; req ++){ + /*just do the requests belonging to the client */ + if (client_requests[req].client_node_id != client_node_id) + continue; + int res_tmp = __execute_mb_request(req); + switch (res_tmp) { + case PORT_FAILURE: { + if (res_tmp != client_nodes[client_node_id].prev_error) + fprintf(stderr, "Modbus plugin: Error connecting Modbus client %%s to remote server.\n", client_nodes[client_node_id].location); + client_nodes[client_node_id].prev_error = res_tmp; + break; + } + case INVALID_FRAME: { + if ((res_tmp != client_requests[req].prev_error) && (0 == client_nodes[client_node_id].prev_error)) + fprintf(stderr, "Modbus plugin: Modbus client request configured at location %%s was unsuccesful. Server/slave returned an invalid/corrupted frame.\n", client_requests[req].location); + client_requests[req].prev_error = res_tmp; + break; + } + case TIMEOUT: { + if ((res_tmp != client_requests[req].prev_error) && (0 == client_nodes[client_node_id].prev_error)) + fprintf(stderr, "Modbus plugin: Modbus client request configured at location %%s timed out waiting for reply from server.\n", client_requests[req].location); + client_requests[req].prev_error = res_tmp; + break; + } + case MODBUS_ERROR: { + if (client_requests[req].prev_error != client_requests[req].error_code) { + fprintf(stderr, "Modbus plugin: Modbus client request configured at location %%s was unsuccesful. Server/slave returned error code 0x%%2x", client_requests[req].location, client_requests[req].error_code); + if (client_requests[req].error_code <= MAX_MODBUS_ERROR_CODE ) { + fprintf(stderr, "(%%s)", modbus_error_messages[client_requests[req].error_code]); + fprintf(stderr, ".\n"); + } + } + client_requests[req].prev_error = client_requests[req].error_code; + break; + } + default: { + if ((res_tmp >= 0) && (client_nodes[client_node_id].prev_error != 0)) { + fprintf(stderr, "Modbus plugin: Modbus client %%s has reconnected to server/slave.\n", client_nodes[client_node_id].location); + } + if ((res_tmp >= 0) && (client_requests[req] .prev_error != 0)) { + fprintf(stderr, "Modbus plugin: Modbus client request configured at location %%s has succesfully resumed comunication.\n", client_requests[req].location); + } + client_nodes[client_node_id].prev_error = 0; + client_requests[req] .prev_error = 0; + break; + } + } + } + // Determine absolute time instant for starting the next cycle + // struct timespec prev_cycle; + // prev_cycle = next_cycle; + next_cycle.tv_sec += period_sec; + next_cycle.tv_nsec += period_nsec; + if (next_cycle.tv_nsec >= 1000000000) { + next_cycle.tv_sec ++; + next_cycle.tv_nsec -= 1000000000; + } + /* It probably does not make sense to check for overflow of timer. + * Even in 32 bit systems this will take at least 68 years since the computer booted + * (remember, we are using CLOCK_MONOTONIC, which should start counting from 0 + * every time the system boots). On 64 bit systems, it will take over + * 10^11 years to overflow. + */ + /* + if (next_cycle.tv_sec) < prev_cycle.tv_sec) { + // we will lose some precision by reading the time again, + // but it is better than the alternative... + clock_gettime(CLOCK_MONOTONIC, &next_cycle); + next_cycle.tv_sec += period_sec; + next_cycle.tv_nsec += period_nsec; + if (next_cycle.tv_nsec >= 1000000000) { + next_cycle.tv_sec ++; + next_cycle.tv_nsec -= 1000000000; + } + } + */ + clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next_cycle, NULL); + } + + // humour the compiler. + return NULL; +} + + +int __cleanup_%(locstr)s (); +int __init_%(locstr)s (int argc, char **argv){ + int index; + + for (index=0; index < NUMBER_OF_CLIENT_NODES;index++) + client_nodes[index].mb_nd = -1; + for (index=0; index < NUMBER_OF_SERVER_NODES;index++) + // mb_nd with negative numbers indicate how far it has been initialised (or not) + // -2 --> no modbus node created; no thread created + // -1 --> modbus node created!; no thread created + // >=0 --> modbus node created!; thread created! + server_nodes[index].mb_nd = -2; + + /* modbus library init */ + /* Note that TOTAL_xxxNODE_COUNT are the nodes required by _ALL_ the instances of the modbus + * extension currently in the user's project. This file (MB_xx.c) is handling only one instance, + * but must initialize the library for all instances. Only the first call to mb_slave_and_master_init() + * will result in memory being allocated. All subsequent calls (by other MB_xx,c files) will be ignored + * by the mb_slave_and_master_init() funtion, as long as they are called with the same arguments. + */ + if (mb_slave_and_master_init(TOTAL_TCPNODE_COUNT, TOTAL_RTUNODE_COUNT, TOTAL_ASCNODE_COUNT) <0) { + fprintf(stderr, "Modbus plugin: Error starting modbus library\n"); + // return imediately. Do NOT goto error_exit, as we did not get to + // start the modbus library! + return -1; + } + + /* init the mutex for each client request */ + /* Must be done _before_ launching the client threads!! */ + for (index=0; index < NUMBER_OF_CLIENT_REQTS; index ++){ + if (pthread_mutex_init(&(client_requests[index].coms_buf_mutex), NULL)) { + fprintf(stderr, "Modbus plugin: Error initializing request for modbus client node %%s\n", client_nodes[client_requests[index].client_node_id].location); + goto error_exit; + } + } + + /* init each client connection to remote modbus server, and launch thread */ + /* NOTE: All client_nodes[].init_state are initialised to 0 in the code + * generated by the modbus plugin + */ + for (index=0; index < NUMBER_OF_CLIENT_NODES;index++){ + /* establish client connection */ + client_nodes[index].mb_nd = mb_master_connect (client_nodes[index].node_address); + if (client_nodes[index].mb_nd < 0){ + fprintf(stderr, "Modbus plugin: Error creating modbus client node %%s\n", client_nodes[index].location); + goto error_exit; + } + client_nodes[index].init_state = 1; // we have created the node + + /* launch a thread to handle this client node */ + { + int res = 0; + pthread_attr_t attr; + res |= pthread_attr_init(&attr); + res |= pthread_create(&(client_nodes[index].thread_id), &attr, &__mb_client_thread, (void *)((char *)NULL + index)); + if (res != 0) { + fprintf(stderr, "Modbus plugin: Error starting modbus client thread for node %%s\n", client_nodes[index].location); + goto error_exit; + } + } + client_nodes[index].init_state = 2; // we have created the node and a thread + } + + /* init each local server */ + /* NOTE: All server_nodes[].init_state are initialised to 0 in the code + * generated by the modbus plugin + */ + for (index=0; index < NUMBER_OF_SERVER_NODES;index++){ + /* create the modbus server */ + server_nodes[index].mb_nd = mb_slave_new (server_nodes[index].node_address); + if (server_nodes[index].mb_nd < 0){ + fprintf(stderr, "Modbus plugin: Error creating modbus server node %%s\n", server_nodes[index].location); + goto error_exit; + } + server_nodes[index].init_state = 1; // we have created the node + + /* launch a thread to handle this server node */ + { + int res = 0; + pthread_attr_t attr; + res |= pthread_attr_init(&attr); + res |= pthread_create(&(server_nodes[index].thread_id), &attr, &__mb_server_thread, (void *)&(server_nodes[index])); + if (res != 0) { + fprintf(stderr, "Modbus plugin: Error starting modbus server thread for node %%s\n", server_nodes[index].location); + goto error_exit; + } + } + server_nodes[index].init_state = 2; // we have created the node and thread + } + + return 0; + +error_exit: + __cleanup_%(locstr)s (); + return -1; +} + + + + + +void __publish_%(locstr)s (){ + int index; + + for (index=0; index < NUMBER_OF_CLIENT_REQTS; index ++){ + /*just do the output requests */ + if (client_requests[index].req_type == req_output){ + pthread_mutex_lock(&(client_requests[index].coms_buf_mutex)); + // copy from plcv_buffer to coms_buffer + memcpy((void *)client_requests[index].coms_buffer /* destination */, + (void *)client_requests[index].plcv_buffer /* source */, + REQ_BUF_SIZE * sizeof(u16) /* size in bytes */); + pthread_mutex_unlock(&(client_requests[index].coms_buf_mutex)); + } + } +} + + + + + +void __retrieve_%(locstr)s (){ + int index; + + for (index=0; index < NUMBER_OF_CLIENT_REQTS; index ++){ + /*just do the input requests */ + if (client_requests[index].req_type == req_input){ + pthread_mutex_lock(&(client_requests[index].coms_buf_mutex)); + // copy from coms_buffer to plcv_buffer + memcpy((void *)client_requests[index].plcv_buffer /* destination */, + (void *)client_requests[index].coms_buffer /* source */, + REQ_BUF_SIZE * sizeof(u16) /* size in bytes */); + pthread_mutex_unlock(&(client_requests[index].coms_buf_mutex)); + } + } +} + + + + + +int __cleanup_%(locstr)s (){ + int index, close; + int res = 0; + + /* kill thread and close connections of each modbus client node */ + for (index=0; index < NUMBER_OF_CLIENT_NODES; index++) { + close = 0; + if (client_nodes[index].init_state >= 2) { + // thread was launched, so we try to cancel it! + close = pthread_cancel(client_nodes[index].thread_id); + close |= pthread_join (client_nodes[index].thread_id, NULL); + if (close < 0) + fprintf(stderr, "Modbus plugin: Error closing thread for modbus client %%s\n", client_nodes[index].location); + } + res |= close; + + close = 0; + if (client_nodes[index].init_state >= 1) { + // modbus client node was created, so we try to close it! + close = mb_master_close (client_nodes[index].mb_nd); + if (close < 0){ + fprintf(stderr, "Modbus plugin: Error closing modbus client node %%s\n", client_nodes[index].location); + // We try to shut down as much as possible, so we do not return noW! + } + client_nodes[index].mb_nd = -1; + } + res |= close; + client_nodes[index].init_state = 0; + } + + /* kill thread and close connections of each modbus server node */ + for (index=0; index < NUMBER_OF_SERVER_NODES; index++) { + close = 0; + if (server_nodes[index].init_state >= 2) { + // thread was launched, so we try to cancel it! + close = pthread_cancel(server_nodes[index].thread_id); + close |= pthread_join (server_nodes[index].thread_id, NULL); + if (close < 0) + fprintf(stderr, "Modbus plugin: Error closing thread for modbus server %%s\n", server_nodes[index].location); + } + res |= close; + + close = 0; + if (server_nodes[index].init_state >= 1) { + // modbus server node was created, so we try to close it! + close = mb_slave_close (server_nodes[index].mb_nd); + if (close < 0) { + fprintf(stderr, "Modbus plugin: Error closing node for modbus server %%s (%%d)\n", server_nodes[index].location, server_nodes[index].mb_nd); + // We try to shut down as much as possible, so we do not return noW! + } + server_nodes[index].mb_nd = -1; + } + res |= close; + server_nodes[index].init_state = 0; + } + + /* destroy the mutex of each client request */ + for (index=0; index < NUMBER_OF_CLIENT_REQTS; index ++) { + if (pthread_mutex_destroy(&(client_requests[index].coms_buf_mutex))) { + fprintf(stderr, "Modbus plugin: Error destroying request for modbus client node %%s\n", client_nodes[client_requests[index].client_node_id].location); + // We try to shut down as much as possible, so we do not return noW! + res |= -1; + } + } + + /* modbus library close */ + //fprintf(stderr, "Shutting down modbus library...\n"); + if (mb_slave_and_master_done()<0) { + fprintf(stderr, "Modbus plugin: Error shutting down modbus library\n"); + res |= -1; + } + + return res; +} + diff -r 38e912c8bd31 -r cc7a46953471 modbus/mb_runtime.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modbus/mb_runtime.h Fri Mar 02 17:01:25 2018 +0100 @@ -0,0 +1,148 @@ +/* File generated by Beremiz (PlugGenerate_C method of modbus Plugin instance) */ + +/* + * Copyright (c) 2016 Mario de Sousa (msousa@fe.up.pt) + * + * This file is part of the Modbus library for Beremiz and matiec. + * + * This Modbus library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this Modbus library. If not, see . + * + * This code is made available on the understanding that it will not be + * used in safety-critical situations without a full and competent review. + */ + +#include "mb_addr.h" +#include "mb_tcp_private.h" +#include "mb_master_private.h" + + + +#define DEF_REQ_SEND_RETRIES 0 + + // Used by the Modbus server node +#define MEM_AREA_SIZE 65536 +typedef struct{ + u16 ro_bits [MEM_AREA_SIZE]; + u16 rw_bits [MEM_AREA_SIZE]; + u16 ro_words[MEM_AREA_SIZE]; + u16 rw_words[MEM_AREA_SIZE]; + } server_mem_t; + +typedef struct{ + const char *location; + u8 slave_id; + node_addr_t node_address; + int mb_nd; // modbus library node used for this server + int init_state; // store how far along the server's initialization has progressed + pthread_t thread_id; // thread handling this server + server_mem_t mem_area; + } server_node_t; + + + // Used by the Modbus client node +typedef struct{ + const char *location; + node_addr_t node_address; + int mb_nd; + int init_state; // store how far along the client's initialization has progressed + u64 comm_period; + int prev_error; // error code of the last printed error message (0 when no error) + pthread_t thread_id; // thread handling all communication with this client + } client_node_t; + + + // Used by the Modbus client plugin +typedef enum { + req_input, + req_output, + no_request /* just for tests to quickly disable a request */ + } iotype_t; + +#define REQ_BUF_SIZE 2000 +typedef struct{ + const char *location; + int client_node_id; + u8 slave_id; + iotype_t req_type; + u8 mb_function; + u16 address; + u16 count; + int retries; + u8 error_code; // modbus error code (if any) of current request + int prev_error; // error code of the last printed error message (0 when no error) + struct timespec resp_timeout; + // buffer used to store located PLC variables + u16 plcv_buffer[REQ_BUF_SIZE]; + // buffer used to store data coming from / going to server + u16 coms_buffer[REQ_BUF_SIZE]; + pthread_mutex_t coms_buf_mutex; // mutex to access coms_buffer[] + } client_request_t; + + +/* The total number of nodes, needed to support _all_ instances of the modbus plugin */ +#define TOTAL_TCPNODE_COUNT %(total_tcpnode_count)s +#define TOTAL_RTUNODE_COUNT %(total_rtunode_count)s +#define TOTAL_ASCNODE_COUNT %(total_ascnode_count)s + +/* Values for instance %(locstr)s of the modbus plugin */ +#define MAX_NUMBER_OF_TCPCLIENTS %(max_remote_tcpclient)s + +#define NUMBER_OF_TCPSERVER_NODES %(tcpserver_node_count)s +#define NUMBER_OF_TCPCLIENT_NODES %(tcpclient_node_count)s +#define NUMBER_OF_TCPCLIENT_REQTS %(tcpclient_reqs_count)s + +#define NUMBER_OF_RTUSERVER_NODES %(rtuserver_node_count)s +#define NUMBER_OF_RTUCLIENT_NODES %(rtuclient_node_count)s +#define NUMBER_OF_RTUCLIENT_REQTS %(rtuclient_reqs_count)s + +#define NUMBER_OF_ASCIISERVER_NODES %(ascserver_node_count)s +#define NUMBER_OF_ASCIICLIENT_NODES %(ascclient_node_count)s +#define NUMBER_OF_ASCIICLIENT_REQTS %(ascclient_reqs_count)s + +#define NUMBER_OF_SERVER_NODES (NUMBER_OF_TCPSERVER_NODES + \ + NUMBER_OF_RTUSERVER_NODES + \ + NUMBER_OF_ASCIISERVER_NODES) + +#define NUMBER_OF_CLIENT_NODES (NUMBER_OF_TCPCLIENT_NODES + \ + NUMBER_OF_RTUCLIENT_NODES + \ + NUMBER_OF_ASCIICLIENT_NODES) + +#define NUMBER_OF_CLIENT_REQTS (NUMBER_OF_TCPCLIENT_REQTS + \ + NUMBER_OF_RTUCLIENT_REQTS + \ + NUMBER_OF_ASCIICLIENT_REQTS) + + +/*initialization following all parameters given by user in application*/ + +static client_node_t client_nodes[NUMBER_OF_CLIENT_NODES] = { +%(client_nodes_params)s +}; + + +static client_request_t client_requests[NUMBER_OF_CLIENT_REQTS] = { +%(client_req_params)s +}; + + +static server_node_t server_nodes[NUMBER_OF_SERVER_NODES] = { +%(server_nodes_params)s +} +; + +/*******************/ +/*located variables*/ +/*******************/ + +%(loc_vars)s + diff -r 38e912c8bd31 -r cc7a46953471 modbus/mb_utils.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modbus/mb_utils.py Fri Mar 02 17:01:25 2018 +0100 @@ -0,0 +1,223 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# This file is part of Beremiz, a Integrated Development Environment for +# programming IEC 61131-3 automates supporting plcopen standard and CanFestival. +# +# Copyright (c) 2016 Mario de Sousa (msousa@fe.up.pt) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# This code is made available on the understanding that it will not be +# used in safety-critical situations without a full and competent review. + + +# dictionary implementing: +# key - string with the description we want in the request plugin GUI +# tuple - (modbus function number, request type, max count value, +# data_type, bit_size) +modbus_function_dict = { + "01 - Read Coils": ('1', 'req_input', 2000, "BOOL", 1, "Q", "X", "Coil"), + "02 - Read Input Discretes": ('2', 'req_input', 2000, "BOOL", 1, "I", "X", "Input Discrete"), + "03 - Read Holding Registers": ('3', 'req_input', 125, "WORD", 16, "Q", "W", "Holding Register"), + "04 - Read Input Registers": ('4', 'req_input', 125, "WORD", 16, "I", "W", "Input Register"), + "05 - Write Single coil": ('5', 'req_output', 1, "BOOL", 1, "Q", "X", "Coil"), + "06 - Write Single Register": ('6', 'req_output', 1, "WORD", 16, "Q", "W", "Holding Register"), + "15 - Write Multiple Coils": ('15', 'req_output', 1968, "BOOL", 1, "Q", "X", "Coil"), + "16 - Write Multiple Registers": ('16', 'req_output', 123, "WORD", 16, "Q", "W", "Holding Register")} + + +# Configuration tree value acces helper +def GetCTVal(child, index): + return child.GetParamsAttributes()[0]["children"][index]["value"] + + +# Configuration tree value acces helper, for multiple values +def GetCTVals(child, indexes): + return map(lambda index: GetCTVal(child, index), indexes) + + +def GetTCPServerNodePrinted(self, child): + """ + Outputs a string to be used on C files + params: child - the correspondent subplugin in Beremiz + """ + node_init_template = '''/*node %(locnodestr)s*/ +{"%(locnodestr)s", %(slaveid)s, {naf_tcp, {.tcp = {%(host)s, "%(port)s", DEF_CLOSE_ON_SILENCE}}}, -1 /* mb_nd */, 0 /* init_state */}''' + + location = ".".join(map(str, child.GetCurrentLocation())) + host, port, slaveid = GetCTVals(child, range(3)) + if host == "#ANY#": + host = 'INADDR_ANY' + else: + host = '"' + host + '"' + # slaveid = GetCTVal(child, 2) + # if int(slaveid) not in xrange(256): + # self.GetCTRoot().logger.write_error("Error: Wrong slave ID in %s server node\nModbus Plugin C code returns empty\n"%location) + # return None + + node_dict = {"locnodestr": location, + "host": host, + "port": port, + "slaveid": slaveid} + return node_init_template % node_dict + + +def GetTCPServerMemAreaPrinted(self, child, nodeid): + """ + Outputs a string to be used on C files + params: child - the correspondent subplugin in Beremiz + nodeid - on C code, each request has it's own parent node (sequential, 0..NUMBER_OF_NODES) + It's this parameter. + return: None - if any definition error found + The string that should be added on C code - if everything goes allright + """ + request_dict = {} + + request_dict["locreqstr"] = "_".join(map(str, child.GetCurrentLocation())) + request_dict["nodeid"] = str(nodeid) + request_dict["address"] = GetCTVal(child, 2) + if int(request_dict["address"]) not in xrange(65536): + self.GetCTRoot().logger.write_error( + "Modbus plugin: Invalid Start Address in server memory area node %(locreqstr)s (Must be in the range [0..65535])\nModbus plugin: Aborting C code generation for this node\n" % request_dict) + return None + request_dict["count"] = GetCTVal(child, 1) + if int(request_dict["count"]) not in xrange(1, 65536): + self.GetCTRoot().logger.write_error( + "Modbus plugin: Invalid number of channels in server memory area node %(locreqstr)s (Must be in the range [1..65536-start_address])\nModbus plugin: Aborting C code generation for this node\n" % request_dict) + return None + if (int(request_dict["address"]) + int(request_dict["count"])) not in xrange(1, 65537): + self.GetCTRoot().logger.write_error( + "Modbus plugin: Invalid number of channels in server memory area node %(locreqstr)s (Must be in the range [1..65536-start_address])\nModbus plugin: Aborting C code generation for this node\n" % request_dict) + return None + + return "" + + +modbus_serial_baudrate_list = [ + "110", "300", "600", "1200", "2400", "4800", "9600", "19200", "38400", "57600", "115200"] +modbus_serial_stopbits_list = ["1", "2"] +modbus_serial_parity_dict = {"none": 0, "odd": 1, "even": 2} + + +def GetRTUSlaveNodePrinted(self, child): + """ + Outputs a string to be used on C files + params: child - the correspondent subplugin in Beremiz + """ + node_init_template = '''/*node %(locnodestr)s*/ +{"%(locnodestr)s", %(slaveid)s, {naf_rtu, {.rtu = {"%(device)s", %(baud)s /*baud*/, %(parity)s /*parity*/, 8 /*data bits*/, %(stopbits)s, 0 /* ignore echo */}}}, -1 /* mb_nd */, 0 /* init_state */}''' + + location = ".".join(map(str, child.GetCurrentLocation())) + device, baud, parity, stopbits, slaveid = GetCTVals(child, range(5)) + + node_dict = {"locnodestr": location, + "device": device, + "baud": baud, + "parity": modbus_serial_parity_dict[parity], + "stopbits": stopbits, + "slaveid": slaveid} + return node_init_template % node_dict + + +def GetRTUClientNodePrinted(self, child): + """ + Outputs a string to be used on C files + params: child - the correspondent subplugin in Beremiz + """ + node_init_template = '''/*node %(locnodestr)s*/ +{"%(locnodestr)s", {naf_rtu, {.rtu = {"%(device)s", %(baud)s /*baud*/, %(parity)s /*parity*/, 8 /*data bits*/, %(stopbits)s, 0 /* ignore echo */}}}, -1 /* mb_nd */, 0 /* init_state */, %(coms_period)s /* communication period */}''' + + location = ".".join(map(str, child.GetCurrentLocation())) + device, baud, parity, stopbits, coms_period = GetCTVals(child, range(5)) + + node_dict = {"locnodestr": location, + "device": device, + "baud": baud, + "parity": modbus_serial_parity_dict[parity], + "stopbits": stopbits, + "coms_period": coms_period} + return node_init_template % node_dict + + +def GetTCPClientNodePrinted(self, child): + """ + Outputs a string to be used on C files + params: child - the correspondent subplugin in Beremiz + """ + node_init_template = '''/*node %(locnodestr)s*/ +{"%(locnodestr)s", {naf_tcp, {.tcp = {"%(host)s", "%(port)s", DEF_CLOSE_ON_SILENCE}}}, -1 /* mb_nd */, 0 /* init_state */, %(coms_period)s /* communication period */, 0 /* prev_error */}''' + + location = ".".join(map(str, child.GetCurrentLocation())) + host, port, coms_period = GetCTVals(child, range(3)) + + node_dict = {"locnodestr": location, + "host": host, + "port": port, + "coms_period": coms_period} + return node_init_template % node_dict + + +def GetClientRequestPrinted(self, child, nodeid): + """ + Outputs a string to be used on C files + params: child - the correspondent subplugin in Beremiz + nodeid - on C code, each request has it's own parent node (sequential, 0..NUMBER_OF_NODES) + It's this parameter. + return: None - if any definition error found + The string that should be added on C code - if everything goes allright + """ + + req_init_template = '''/*request %(locreqstr)s*/ +{"%(locreqstr)s", %(nodeid)s, %(slaveid)s, %(iotype)s, %(func_nr)s, %(address)s , %(count)s, +DEF_REQ_SEND_RETRIES, 0 /* error_code */, 0 /* prev_code */, {%(timeout_s)d, %(timeout_ns)d} /* timeout */, +{%(buffer)s}, {%(buffer)s}}''' + + timeout = int(GetCTVal(child, 4)) + timeout_s = int(timeout / 1000) + timeout_ms = timeout - (timeout_s * 1000) + timeout_ns = timeout_ms * 1000000 + + request_dict = { + "locreqstr": "_".join(map(str, child.GetCurrentLocation())), + "nodeid": str(nodeid), + "slaveid": GetCTVal(child, 1), + "address": GetCTVal(child, 3), + "count": GetCTVal(child, 2), + "timeout": timeout, + "timeout_s": timeout_s, + "timeout_ns": timeout_ns, + "buffer": ",".join(['0'] * int(GetCTVal(child, 2))), + "func_nr": modbus_function_dict[GetCTVal(child, 0)][0], + "iotype": modbus_function_dict[GetCTVal(child, 0)][1], + "maxcount": modbus_function_dict[GetCTVal(child, 0)][2]} + + if int(request_dict["slaveid"]) not in xrange(256): + self.GetCTRoot().logger.write_error( + "Modbus plugin: Invalid slaveID in TCP client request node %(locreqstr)s (Must be in the range [0..255])\nModbus plugin: Aborting C code generation for this node\n" % request_dict) + return None + if int(request_dict["address"]) not in xrange(65536): + self.GetCTRoot().logger.write_error( + "Modbus plugin: Invalid Start Address in TCP client request node %(locreqstr)s (Must be in the range [0..65535])\nModbus plugin: Aborting C code generation for this node\n" % request_dict) + return None + if int(request_dict["count"]) not in xrange(1, 1 + int(request_dict["maxcount"])): + self.GetCTRoot().logger.write_error( + "Modbus plugin: Invalid number of channels in TCP client request node %(locreqstr)s (Must be in the range [1..%(maxcount)s])\nModbus plugin: Aborting C code generation for this node\n" % request_dict) + return None + if (int(request_dict["address"]) + int(request_dict["count"])) not in xrange(1, 65537): + self.GetCTRoot().logger.write_error( + "Modbus plugin: Invalid number of channels in TCP client request node %(locreqstr)s (start_address + nr_channels must be less than 65536)\nModbus plugin: Aborting C code generation for this node\n" % request_dict) + return None + + return req_init_template % request_dict diff -r 38e912c8bd31 -r cc7a46953471 modbus/modbus.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modbus/modbus.py Fri Mar 02 17:01:25 2018 +0100 @@ -0,0 +1,804 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# This file is part of Beremiz, a Integrated Development Environment for +# programming IEC 61131-3 automates supporting plcopen standard and CanFestival. +# +# Copyright (c) 2016 Mario de Sousa (msousa@fe.up.pt) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# This code is made available on the understanding that it will not be +# used in safety-critical situations without a full and competent review. + + +from __future__ import absolute_import +import os +from modbus.mb_utils import * + +from ConfigTreeNode import ConfigTreeNode +from PLCControler import LOCATION_CONFNODE, LOCATION_VAR_MEMORY + +base_folder = os.path.split(os.path.dirname(os.path.realpath(__file__)))[0] +base_folder = os.path.join(base_folder, "..") +ModbusPath = os.path.join(base_folder, "Modbus") + + +# +# +# +# C L I E N T R E Q U E S T # +# +# +# + + +class _RequestPlug(object): + XSD = """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + """ + + def GetParamsAttributes(self, path=None): + infos = ConfigTreeNode.GetParamsAttributes(self, path=path) + for element in infos: + if element["name"] == "ModbusRequest": + for child in element["children"]: + if child["name"] == "Function": + list = modbus_function_dict.keys() + list.sort() + child["type"] = list + return infos + + def GetVariableLocationTree(self): + current_location = self.GetCurrentLocation() + name = self.BaseParams.getName() + address = self.GetParamsAttributes()[0]["children"][3]["value"] + count = self.GetParamsAttributes()[0]["children"][2]["value"] + function = self.GetParamsAttributes()[0]["children"][0]["value"] + # 'BOOL' or 'WORD' + datatype = modbus_function_dict[function][3] + # 1 or 16 + datasize = modbus_function_dict[function][4] + # 'Q' for coils and holding registers, 'I' for input discretes and input registers + # datazone = modbus_function_dict[function][5] + # 'X' for bits, 'W' for words + datatacc = modbus_function_dict[function][6] + # 'Coil', 'Holding Register', 'Input Discrete' or 'Input Register' + dataname = modbus_function_dict[function][7] + entries = [] + for offset in range(address, address + count): + entries.append({ + "name": dataname + " " + str(offset), + "type": LOCATION_VAR_MEMORY, + "size": datasize, + "IEC_type": datatype, + "var_name": "var_name", + "location": datatacc + ".".join([str(i) for i in current_location]) + "." + str(offset), + "description": "description", + "children": []}) + return {"name": name, + "type": LOCATION_CONFNODE, + "location": ".".join([str(i) for i in current_location]) + ".x", + "children": entries} + + def CTNGenerate_C(self, buildpath, locations): + """ + Generate C code + @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5) + @param locations: List of complete variables locations \ + [{"IEC_TYPE" : the IEC type (i.e. "INT", "STRING", ...) + "NAME" : name of the variable (generally "__IW0_1_2" style) + "DIR" : direction "Q","I" or "M" + "SIZE" : size "X", "B", "W", "D", "L" + "LOC" : tuple of interger for IEC location (0,1,2,...) + }, ...] + @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND + """ + return [], "", False + + +# +# +# +# S E R V E R M E M O R Y A R E A # +# +# +# + +# dictionary implementing: +# key - string with the description we want in the request plugin GUI +# list - (modbus function number, request type, max count value) +modbus_memtype_dict = { + "01 - Coils": ('1', 'rw_bits', 65536, "BOOL", 1, "Q", "X", "Coil"), + "02 - Input Discretes": ('2', 'ro_bits', 65536, "BOOL", 1, "I", "X", "Input Discrete"), + "03 - Holding Registers": ('3', 'rw_words', 65536, "WORD", 16, "Q", "W", "Holding Register"), + "04 - Input Registers": ('4', 'ro_words', 65536, "WORD", 16, "I", "W", "Input Register"), +} + + +class _MemoryAreaPlug(object): + XSD = """ + + + + + + + + + + + + + + + + + + + + + + + + """ + + def GetParamsAttributes(self, path=None): + infos = ConfigTreeNode.GetParamsAttributes(self, path=path) + for element in infos: + if element["name"] == "MemoryArea": + for child in element["children"]: + if child["name"] == "MemoryAreaType": + list = modbus_memtype_dict.keys() + list.sort() + child["type"] = list + return infos + + def GetVariableLocationTree(self): + current_location = self.GetCurrentLocation() + name = self.BaseParams.getName() + address = self.GetParamsAttributes()[0]["children"][2]["value"] + count = self.GetParamsAttributes()[0]["children"][1]["value"] + function = self.GetParamsAttributes()[0]["children"][0]["value"] + # 'BOOL' or 'WORD' + datatype = modbus_memtype_dict[function][3] + # 1 or 16 + datasize = modbus_memtype_dict[function][4] + # 'Q' for coils and holding registers, 'I' for input discretes and input registers + # datazone = modbus_memtype_dict[function][5] + # 'X' for bits, 'W' for words + datatacc = modbus_memtype_dict[function][6] + # 'Coil', 'Holding Register', 'Input Discrete' or 'Input Register' + dataname = modbus_memtype_dict[function][7] + entries = [] + for offset in range(address, address + count): + entries.append({ + "name": dataname + " " + str(offset), + "type": LOCATION_VAR_MEMORY, + "size": datasize, + "IEC_type": datatype, + "var_name": "var_name", + "location": datatacc + ".".join([str(i) for i in current_location]) + "." + str(offset), + "description": "description", + "children": []}) + return {"name": name, + "type": LOCATION_CONFNODE, + "location": ".".join([str(i) for i in current_location]) + ".x", + "children": entries} + + def CTNGenerate_C(self, buildpath, locations): + """ + Generate C code + @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5) + @param locations: List of complete variables locations \ + [{"IEC_TYPE" : the IEC type (i.e. "INT", "STRING", ...) + "NAME" : name of the variable (generally "__IW0_1_2" style) + "DIR" : direction "Q","I" or "M" + "SIZE" : size "X", "B", "W", "D", "L" + "LOC" : tuple of interger for IEC location (0,1,2,...) + }, ...] + @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND + """ + return [], "", False + + +# +# +# +# T C P C L I E N T # +# +# +# + +class _ModbusTCPclientPlug(object): + XSD = """ + + + + + + + + + + + + + + + + + """ + # NOTE: Max value of 2147483647 (i32_max) for Invocation_Rate_in_ms + # corresponds to aprox 25 days. + CTNChildrenTypes = [("ModbusRequest", _RequestPlug, "Request")] + # TODO: Replace with CTNType !!! + PlugType = "ModbusTCPclient" + + # Return the number of (modbus library) nodes this specific TCP client will need + # return type: (tcp nodes, rtu nodes, ascii nodes) + def GetNodeCount(self): + return (1, 0, 0) + + def CTNGenerate_C(self, buildpath, locations): + """ + Generate C code + @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5) + @param locations: List of complete variables locations \ + [{"IEC_TYPE" : the IEC type (i.e. "INT", "STRING", ...) + "NAME" : name of the variable (generally "__IW0_1_2" style) + "DIR" : direction "Q","I" or "M" + "SIZE" : size "X", "B", "W", "D", "L" + "LOC" : tuple of interger for IEC location (0,1,2,...) + }, ...] + @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND + """ + return [], "", False + + +# +# +# +# T C P S E R V E R # +# +# +# + +class _ModbusTCPserverPlug(object): + # NOTE: the Port number is a 'string' and not an 'integer'! + # This is because the underlying modbus library accepts strings + # (e.g.: well known port names!) + XSD = """ + + + + + + + + + + + + + + + + + """ + CTNChildrenTypes = [("MemoryArea", _MemoryAreaPlug, "Memory Area")] + # TODO: Replace with CTNType !!! + PlugType = "ModbusTCPserver" + + # Return the number of (modbus library) nodes this specific TCP server will need + # return type: (tcp nodes, rtu nodes, ascii nodes) + def GetNodeCount(self): + return (1, 0, 0) + + # Return a list with a single tuple conatining the (location, port number) + # location: location of this node in the configuration tree + # port number: IP port used by this Modbus/IP server + def GetIPServerPortNumbers(self): + port = self.GetParamsAttributes()[0]["children"][1]["value"] + return [(self.GetCurrentLocation(), port)] + + def CTNGenerate_C(self, buildpath, locations): + """ + Generate C code + @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5) + @param locations: List of complete variables locations \ + [{"IEC_TYPE" : the IEC type (i.e. "INT", "STRING", ...) + "NAME" : name of the variable (generally "__IW0_1_2" style) + "DIR" : direction "Q","I" or "M" + "SIZE" : size "X", "B", "W", "D", "L" + "LOC" : tuple of interger for IEC location (0,1,2,...) + }, ...] + @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND + """ + return [], "", False + + +# +# +# +# R T U C L I E N T # +# +# +# + +class _ModbusRTUclientPlug(object): + XSD = """ + + + + + + + + + + + + + + + + + + + """ + # NOTE: Max value of 2147483647 (i32_max) for Invocation_Rate_in_ms + # corresponds to aprox 25 days. + CTNChildrenTypes = [("ModbusRequest", _RequestPlug, "Request")] + # TODO: Replace with CTNType !!! + PlugType = "ModbusRTUclient" + + def GetParamsAttributes(self, path=None): + infos = ConfigTreeNode.GetParamsAttributes(self, path=path) + for element in infos: + if element["name"] == "ModbusRTUclient": + for child in element["children"]: + if child["name"] == "Baud_Rate": + child["type"] = modbus_serial_baudrate_list + if child["name"] == "Stop_Bits": + child["type"] = modbus_serial_stopbits_list + if child["name"] == "Parity": + child["type"] = modbus_serial_parity_dict.keys() + return infos + + # Return the number of (modbus library) nodes this specific RTU client will need + # return type: (tcp nodes, rtu nodes, ascii nodes) + def GetNodeCount(self): + return (0, 1, 0) + + def CTNGenerate_C(self, buildpath, locations): + """ + Generate C code + @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5) + @param locations: List of complete variables locations \ + [{"IEC_TYPE" : the IEC type (i.e. "INT", "STRING", ...) + "NAME" : name of the variable (generally "__IW0_1_2" style) + "DIR" : direction "Q","I" or "M" + "SIZE" : size "X", "B", "W", "D", "L" + "LOC" : tuple of interger for IEC location (0,1,2,...) + }, ...] + @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND + """ + return [], "", False + + +# +# +# +# R T U S L A V E # +# +# +# + + +class _ModbusRTUslavePlug(object): + XSD = """ + + + + + + + + + + + + + + + + + + + """ + CTNChildrenTypes = [("MemoryArea", _MemoryAreaPlug, "Memory Area")] + # TODO: Replace with CTNType !!! + PlugType = "ModbusRTUslave" + + def GetParamsAttributes(self, path=None): + infos = ConfigTreeNode.GetParamsAttributes(self, path=path) + for element in infos: + if element["name"] == "ModbusRTUslave": + for child in element["children"]: + if child["name"] == "Baud_Rate": + child["type"] = modbus_serial_baudrate_list + if child["name"] == "Stop_Bits": + child["type"] = modbus_serial_stopbits_list + if child["name"] == "Parity": + child["type"] = modbus_serial_parity_dict.keys() + return infos + + # Return the number of (modbus library) nodes this specific RTU slave will need + # return type: (tcp nodes, rtu nodes, ascii nodes) + def GetNodeCount(self): + return (0, 1, 0) + + def CTNGenerate_C(self, buildpath, locations): + """ + Generate C code + @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5) + @param locations: List of complete variables locations \ + [{"IEC_TYPE" : the IEC type (i.e. "INT", "STRING", ...) + "NAME" : name of the variable (generally "__IW0_1_2" style) + "DIR" : direction "Q","I" or "M" + "SIZE" : size "X", "B", "W", "D", "L" + "LOC" : tuple of interger for IEC location (0,1,2,...) + }, ...] + @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND + """ + return [], "", False + + +def _lt_to_str(loctuple): + return '.'.join(map(str, loctuple)) + + +# +# +# +# R O O T C L A S S # +# +# +# +class RootClass(object): + XSD = """ + + + + + + + + + + + + + + + """ + CTNChildrenTypes = [("ModbusTCPclient", _ModbusTCPclientPlug, "Modbus TCP Client"), + ("ModbusTCPserver", _ModbusTCPserverPlug, "Modbus TCP Server"), + ("ModbusRTUclient", _ModbusRTUclientPlug, "Modbus RTU Client"), + ("ModbusRTUslave", _ModbusRTUslavePlug, "Modbus RTU Slave")] + + # Return the number of (modbus library) nodes this specific instance of the modbus plugin will need + # return type: (tcp nodes, rtu nodes, ascii nodes) + def GetNodeCount(self): + max_remote_tcpclient = self.GetParamsAttributes()[ + 0]["children"][0]["value"] + total_node_count = (max_remote_tcpclient, 0, 0) + for child in self.IECSortedChildren(): + # ask each child how many nodes it needs, and add them all up. + total_node_count = tuple( + x1 + x2 for x1, x2 in zip(total_node_count, child.GetNodeCount())) + return total_node_count + + # Return a list with tuples of the (location, port numbers) used by all + # the Modbus/IP servers + def GetIPServerPortNumbers(self): + IPServer_port_numbers = [] + for child in self.IECSortedChildren(): + if child.CTNType == "ModbusTCPserver": + IPServer_port_numbers.extend(child.GetIPServerPortNumbers()) + return IPServer_port_numbers + + def CTNGenerate_C(self, buildpath, locations): + # print "#############" + # print self.__class__ + # print type(self) + # print "self.CTNType >>>" + # print self.CTNType + # print "type(self.CTNType) >>>" + # print type(self.CTNType) + # print "#############" + + loc_dict = {"locstr": "_".join(map(str, self.GetCurrentLocation()))} + + # Determine the number of (modbus library) nodes ALL instances of the modbus plugin will need + # total_node_count: (tcp nodes, rtu nodes, ascii nodes) + # Also get a list with tuples of (location, IP port numbers) used by all the Modbus/IP server nodes + # This list is later used to search for duplicates in port numbers! + # IPServer_port_numbers = [(location ,IPserver_port_number), ...] + # location: tuple similar to (0, 3, 1) representing the location in the configuration tree "0.3.1.x" + # IPserver_port_number: a number (i.e. port number used by the + # Modbus/IP server) + total_node_count = (0, 0, 0) + IPServer_port_numbers = [] + for CTNInstance in self.GetCTRoot().IterChildren(): + if CTNInstance.CTNType == "modbus": + # ask each modbus plugin instance how many nodes it needs, and + # add them all up. + total_node_count = tuple(x1 + x2 for x1, x2 in zip( + total_node_count, CTNInstance.GetNodeCount())) + IPServer_port_numbers.extend( + CTNInstance.GetIPServerPortNumbers()) + + # Search for use of duplicate port numbers by Modbus/IP servers + # print IPServer_port_numbers + # ..but first define a lambda function to convert a tuple with the config tree location to a nice looking string + # for e.g., convert the tuple (0, 3, 4) to "0.3.4" + + for i in range(0, len(IPServer_port_numbers) - 1): + for j in range(i + 1, len(IPServer_port_numbers)): + if IPServer_port_numbers[i][1] == IPServer_port_numbers[j][1]: + self.GetCTRoot().logger.write_warning( + _("Error: Modbus/IP Servers %s.x and %s.x use the same port number %s.\n") % ( + _lt_to_str(IPServer_port_numbers[i][0]), + _lt_to_str(IPServer_port_numbers[j][0]), + IPServer_port_numbers[j][1])) + raise Exception + # TODO: return an error code instead of raising an + # exception + + # Determine the current location in Beremiz's project configuration + # tree + current_location = self.GetCurrentLocation() + + # define a unique name for the generated C and h files + prefix = "_".join(map(str, current_location)) + Gen_MB_c_path = os.path.join(buildpath, "MB_%s.c" % prefix) + Gen_MB_h_path = os.path.join(buildpath, "MB_%s.h" % prefix) + c_filename = os.path.join(os.path.split(__file__)[0], "mb_runtime.c") + h_filename = os.path.join(os.path.split(__file__)[0], "mb_runtime.h") + + tcpclient_reqs_count = 0 + rtuclient_reqs_count = 0 + ascclient_reqs_count = 0 + tcpclient_node_count = 0 + rtuclient_node_count = 0 + ascclient_node_count = 0 + tcpserver_node_count = 0 + rtuserver_node_count = 0 + ascserver_node_count = 0 + nodeid = 0 + client_nodeid = 0 + client_requestid = 0 + server_id = 0 + + server_node_list = [] + client_node_list = [] + client_request_list = [] + server_memarea_list = [] + loc_vars = [] + loc_vars_list = [] # list of variables already declared in C code! + for child in self.IECSortedChildren(): + # print "<<<<<<<<<<<<<" + # print "child (self.IECSortedChildren())----->" + # print child.__class__ + # print ">>>>>>>>>>>>>" + # + if child.PlugType == "ModbusTCPserver": + tcpserver_node_count += 1 + new_node = GetTCPServerNodePrinted(self, child) + if new_node is None: + return [], "", False + server_node_list.append(new_node) + # + for subchild in child.IECSortedChildren(): + new_memarea = GetTCPServerMemAreaPrinted( + self, subchild, nodeid) + if new_memarea is None: + return [], "", False + server_memarea_list.append(new_memarea) + function = subchild.GetParamsAttributes()[ + 0]["children"][0]["value"] + # 'ro_bits', 'rw_bits', 'ro_words' or 'rw_words' + memarea = modbus_memtype_dict[function][1] + for iecvar in subchild.GetLocations(): + # print repr(iecvar) + absloute_address = iecvar["LOC"][3] + start_address = int(GetCTVal(subchild, 2)) + relative_addr = absloute_address - start_address + # test if relative address in request specified range + if relative_addr in xrange(int(GetCTVal(subchild, 1))): + if str(iecvar["NAME"]) not in loc_vars_list: + loc_vars.append("u16 *" + str(iecvar["NAME"]) + " = &server_nodes[%d].mem_area.%s[%d];" % ( + server_id, memarea, absloute_address)) + loc_vars_list.append(str(iecvar["NAME"])) + server_id += 1 + # + if child.PlugType == "ModbusRTUslave": + rtuserver_node_count += 1 + new_node = GetRTUSlaveNodePrinted(self, child) + if new_node is None: + return [], "", False + server_node_list.append(new_node) + # + for subchild in child.IECSortedChildren(): + new_memarea = GetTCPServerMemAreaPrinted( + self, subchild, nodeid) + if new_memarea is None: + return [], "", False + server_memarea_list.append(new_memarea) + function = subchild.GetParamsAttributes()[ + 0]["children"][0]["value"] + # 'ro_bits', 'rw_bits', 'ro_words' or 'rw_words' + memarea = modbus_memtype_dict[function][1] + for iecvar in subchild.GetLocations(): + # print repr(iecvar) + absloute_address = iecvar["LOC"][3] + start_address = int(GetCTVal(subchild, 2)) + relative_addr = absloute_address - start_address + # test if relative address in request specified range + if relative_addr in xrange(int(GetCTVal(subchild, 1))): + if str(iecvar["NAME"]) not in loc_vars_list: + loc_vars.append("u16 *" + str(iecvar["NAME"]) + " = &server_nodes[%d].mem_area.%s[%d];" % ( + server_id, memarea, absloute_address)) + loc_vars_list.append(str(iecvar["NAME"])) + server_id += 1 + # + if child.PlugType == "ModbusTCPclient": + tcpclient_reqs_count += len(child.IECSortedChildren()) + new_node = GetTCPClientNodePrinted(self, child) + if new_node is None: + return [], "", False + client_node_list.append(new_node) + for subchild in child.IECSortedChildren(): + new_req = GetClientRequestPrinted( + self, subchild, client_nodeid) + if new_req is None: + return [], "", False + client_request_list.append(new_req) + for iecvar in subchild.GetLocations(): + # absloute address - start address + relative_addr = iecvar["LOC"][3] - int(GetCTVal(subchild, 3)) + # test if relative address in request specified range + if relative_addr in xrange(int(GetCTVal(subchild, 2))): + if str(iecvar["NAME"]) not in loc_vars_list: + loc_vars.append( + "u16 *" + str(iecvar["NAME"]) + " = &client_requests[%d].plcv_buffer[%d];" % (client_requestid, relative_addr)) + loc_vars_list.append(str(iecvar["NAME"])) + client_requestid += 1 + tcpclient_node_count += 1 + client_nodeid += 1 + # + if child.PlugType == "ModbusRTUclient": + rtuclient_reqs_count += len(child.IECSortedChildren()) + new_node = GetRTUClientNodePrinted(self, child) + if new_node is None: + return [], "", False + client_node_list.append(new_node) + for subchild in child.IECSortedChildren(): + new_req = GetClientRequestPrinted( + self, subchild, client_nodeid) + if new_req is None: + return [], "", False + client_request_list.append(new_req) + for iecvar in subchild.GetLocations(): + # absloute address - start address + relative_addr = iecvar["LOC"][3] - int(GetCTVal(subchild, 3)) + # test if relative address in request specified range + if relative_addr in xrange(int(GetCTVal(subchild, 2))): + if str(iecvar["NAME"]) not in loc_vars_list: + loc_vars.append( + "u16 *" + str(iecvar["NAME"]) + " = &client_requests[%d].plcv_buffer[%d];" % (client_requestid, relative_addr)) + loc_vars_list.append(str(iecvar["NAME"])) + client_requestid += 1 + rtuclient_node_count += 1 + client_nodeid += 1 + nodeid += 1 + + loc_dict["loc_vars"] = "\n".join(loc_vars) + loc_dict["server_nodes_params"] = ",\n\n".join(server_node_list) + loc_dict["client_nodes_params"] = ",\n\n".join(client_node_list) + loc_dict["client_req_params"] = ",\n\n".join(client_request_list) + loc_dict["tcpclient_reqs_count"] = str(tcpclient_reqs_count) + loc_dict["tcpclient_node_count"] = str(tcpclient_node_count) + loc_dict["tcpserver_node_count"] = str(tcpserver_node_count) + loc_dict["rtuclient_reqs_count"] = str(rtuclient_reqs_count) + loc_dict["rtuclient_node_count"] = str(rtuclient_node_count) + loc_dict["rtuserver_node_count"] = str(rtuserver_node_count) + loc_dict["ascclient_reqs_count"] = str(ascclient_reqs_count) + loc_dict["ascclient_node_count"] = str(ascclient_node_count) + loc_dict["ascserver_node_count"] = str(ascserver_node_count) + loc_dict["total_tcpnode_count"] = str(total_node_count[0]) + loc_dict["total_rtunode_count"] = str(total_node_count[1]) + loc_dict["total_ascnode_count"] = str(total_node_count[2]) + loc_dict["max_remote_tcpclient"] = int( + self.GetParamsAttributes()[0]["children"][0]["value"]) + + # get template file content into a string, format it with dict + # and write it to proper .h file + mb_main = open(h_filename).read() % loc_dict + f = open(Gen_MB_h_path, 'w') + f.write(mb_main) + f.close() + # same thing as above, but now to .c file + mb_main = open(c_filename).read() % loc_dict + f = open(Gen_MB_c_path, 'w') + f.write(mb_main) + f.close() + + LDFLAGS = [] + LDFLAGS.append(" \"-L" + ModbusPath + "\"") + LDFLAGS.append(" -lmb ") + LDFLAGS.append(" \"-Wl,-rpath," + ModbusPath + "\"") + # LDFLAGS.append("\"" + os.path.join(ModbusPath, "mb_slave_and_master.o") + "\"") + # LDFLAGS.append("\"" + os.path.join(ModbusPath, "mb_slave.o") + "\"") + # LDFLAGS.append("\"" + os.path.join(ModbusPath, "mb_master.o") + "\"") + # LDFLAGS.append("\"" + os.path.join(ModbusPath, "mb_tcp.o") + "\"") + # LDFLAGS.append("\"" + os.path.join(ModbusPath, "mb_rtu.o") + "\"") + # LDFLAGS.append("\"" + os.path.join(ModbusPath, "mb_ascii.o") + "\"") + # LDFLAGS.append("\"" + os.path.join(ModbusPath, "sin_util.o") + "\"") + # Target is ARM with linux and not win on x86 so winsock2 (ws2_32) library is useless !!! + # if os.name == 'nt': # other possible values: 'posix' 'os2' 'ce' 'java' 'riscos' + # LDFLAGS.append(" -lws2_32 ") # on windows we need to load winsock + # library! + + return [(Gen_MB_c_path, ' -I"' + ModbusPath + '"')], LDFLAGS, True diff -r 38e912c8bd31 -r cc7a46953471 plcopen/BlockInstanceCollector.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plcopen/BlockInstanceCollector.py Fri Mar 02 17:01:25 2018 +0100 @@ -0,0 +1,188 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# This file is part of Beremiz. +# See COPYING file for copyrights details. + +from __future__ import absolute_import +from collections import OrderedDict, namedtuple +from plcopen.XSLTModelQuery import XSLTModelQuery, _StringValue, _BoolValue, _translate_args + +# ------------------------------------------------------------------------------- +# Helpers object for generating pou block instances list +# ------------------------------------------------------------------------------- + + +_Point = namedtuple("Point", ["x", "y"]) + +_BlockInstanceInfos = namedtuple( + "BlockInstanceInfos", + ["type", "id", "x", "y", "width", "height", "specific_values", "inputs", "outputs"]) + +_BlockSpecificValues = ( + namedtuple("BlockSpecificValues", + ["name", "execution_order"]), + [_StringValue, int]) +_VariableSpecificValues = ( + namedtuple("VariableSpecificValues", + ["name", "value_type", "execution_order"]), + [_StringValue, _StringValue, int]) +_ConnectionSpecificValues = ( + namedtuple("ConnectionSpecificValues", ["name"]), + [_StringValue]) + +_PowerRailSpecificValues = ( + namedtuple("PowerRailSpecificValues", ["connectors"]), + [int]) + +_LDElementSpecificValues = ( + namedtuple("LDElementSpecificValues", + ["name", "negated", "edge", "storage", "execution_order"]), + [_StringValue, _BoolValue, _StringValue, _StringValue, int]) + +_DivergenceSpecificValues = ( + namedtuple("DivergenceSpecificValues", ["connectors"]), + [int]) + +_SpecificValuesTuples = { + "comment": ( + namedtuple("CommentSpecificValues", ["content"]), + [_StringValue]), + "input": _VariableSpecificValues, + "output": _VariableSpecificValues, + "inout": _VariableSpecificValues, + "connector": _ConnectionSpecificValues, + "continuation": _ConnectionSpecificValues, + "leftPowerRail": _PowerRailSpecificValues, + "rightPowerRail": _PowerRailSpecificValues, + "contact": _LDElementSpecificValues, + "coil": _LDElementSpecificValues, + "step": ( + namedtuple("StepSpecificValues", ["name", "initial", "action"]), + [_StringValue, _BoolValue, lambda x: x]), + "transition": ( + namedtuple("TransitionSpecificValues", + ["priority", "condition_type", "condition", "connection"]), + [int, _StringValue, _StringValue, lambda x: x]), + "selectionDivergence": _DivergenceSpecificValues, + "selectionConvergence": _DivergenceSpecificValues, + "simultaneousDivergence": _DivergenceSpecificValues, + "simultaneousConvergence": _DivergenceSpecificValues, + "jump": ( + namedtuple("JumpSpecificValues", ["target"]), + [_StringValue]), + "actionBlock": ( + namedtuple("ActionBlockSpecificValues", ["actions"]), + [lambda x: x]), +} + +_InstanceConnectionInfos = namedtuple( + "InstanceConnectionInfos", + ["name", "negated", "edge", "position", "links"]) + +_ConnectionLinkInfos = namedtuple( + "ConnectionLinkInfos", + ["refLocalId", "formalParameter", "points"]) + + +class _ActionInfos(object): + __slots__ = ["qualifier", "type", "value", "duration", "indicator"] + + def __init__(self, *args): + for attr, value in zip(self.__slots__, args): + setattr(self, attr, value if value is not None else "") + + def copy(self): + return _ActionInfos(*[getattr(self, attr) for attr in self.__slots__]) + + +class BlockInstanceFactory(object): + + def __init__(self, block_instances): + self.BlockInstances = block_instances + self.CurrentInstance = None + self.SpecificValues = None + self.CurrentConnection = None + self.CurrentLink = None + + def SetSpecificValues(self, context, *args): + self.SpecificValues = list(args) + self.CurrentInstance = None + self.CurrentConnection = None + self.CurrentLink = None + + def AddBlockInstance(self, context, *args): + specific_values_tuple, specific_values_translation = \ + _SpecificValuesTuples.get(args[0][0], _BlockSpecificValues) + + if args[0][0] == "step" and len(self.SpecificValues) < 3 or \ + args[0][0] == "transition" and len(self.SpecificValues) < 4: + self.SpecificValues.append([None]) + elif args[0][0] == "actionBlock" and len(self.SpecificValues) < 1: + self.SpecificValues.append([[]]) + specific_values = specific_values_tuple(*_translate_args( + specific_values_translation, self.SpecificValues)) + self.SpecificValues = None + + self.CurrentInstance = _BlockInstanceInfos( + *(_translate_args([_StringValue, int] + [float] * 4, args) + + [specific_values, [], []])) + + self.BlockInstances[self.CurrentInstance.id] = self.CurrentInstance + + def AddInstanceConnection(self, context, *args): + connection_args = _translate_args( + [_StringValue] * 2 + [_BoolValue, _StringValue] + [float] * 2, args) + + self.CurrentConnection = _InstanceConnectionInfos( + *(connection_args[1:4] + [ + _Point(*connection_args[4:6]), []])) + + if self.CurrentInstance is not None: + if connection_args[0] == "input": + self.CurrentInstance.inputs.append(self.CurrentConnection) + else: + self.CurrentInstance.outputs.append(self.CurrentConnection) + else: + self.SpecificValues.append([self.CurrentConnection]) + + def AddConnectionLink(self, context, *args): + self.CurrentLink = _ConnectionLinkInfos( + *(_translate_args([int, _StringValue], args) + [[]])) + self.CurrentConnection.links.append(self.CurrentLink) + + def AddLinkPoint(self, context, *args): + self.CurrentLink.points.append(_Point( + *_translate_args([float] * 2, args))) + + def AddAction(self, context, *args): + if len(self.SpecificValues) == 0: + self.SpecificValues.append([[]]) + translated_args = _translate_args([_StringValue] * 5, args) + self.SpecificValues[0][0].append(_ActionInfos(*translated_args)) + + +class BlockInstanceCollector(XSLTModelQuery): + """ object for collecting instances path list""" + def __init__(self, controller): + XSLTModelQuery.__init__(self, + controller, + "pou_block_instances.xslt", + [(name, self.FactoryCaller(name)) + for name in ["AddBlockInstance", + "SetSpecificValues", + "AddInstanceConnection", + "AddConnectionLink", + "AddLinkPoint", + "AddAction"]]) + + def FactoryCaller(self, funcname): + def CallFactory(*args): + return getattr(self.factory, funcname)(*args) + return CallFactory + + def Collect(self, root, debug): + element_instances = OrderedDict() + self.factory = BlockInstanceFactory(element_instances) + self._process_xslt(root, debug) + self.factory = None + return element_instances diff -r 38e912c8bd31 -r cc7a46953471 plcopen/InstanceTagnameCollector.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plcopen/InstanceTagnameCollector.py Fri Mar 02 17:01:25 2018 +0100 @@ -0,0 +1,59 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# This file is part of Beremiz. +# See COPYING file for copyrights details. + +from __future__ import absolute_import +from plcopen.XSLTModelQuery import XSLTModelQuery +from plcopen.types_enums import * + + +class InstanceTagName(object): + """Helpers object for generating instance tagname""" + + def __init__(self): + self.TagName = None + + def GetTagName(self): + return self.TagName + + def ConfigTagName(self, context, *args): + self.TagName = ComputeConfigurationName(args[0][0]) + + def ResourceTagName(self, context, *args): + self.TagName = ComputeConfigurationResourceName(args[0][0], args[1][0]) + + def PouTagName(self, context, *args): + self.TagName = ComputePouName(args[0][0]) + + def ActionTagName(self, context, *args): + self.TagName = ComputePouActionName(args[0][0], args[0][1]) + + def TransitionTagName(self, context, *args): + self.TagName = ComputePouTransitionName(args[0][0], args[0][1]) + + +class InstanceTagnameCollector(XSLTModelQuery): + """ object for collecting instances path list""" + def __init__(self, controller): + XSLTModelQuery.__init__(self, + controller, + "instance_tagname.xslt", + [(name, self.FactoryCaller(name)) + for name in ["ConfigTagName", + "ResourceTagName", + "PouTagName", + "ActionTagName", + "TransitionTagName"]]) + + def FactoryCaller(self, funcname): + def CallFactory(*args): + return getattr(self.factory, funcname)(*args) + return CallFactory + + def Collect(self, root, debug, instance_path): + self.factory = InstanceTagName() + self._process_xslt(root, debug, instance_path=instance_path) + res = self.factory.GetTagName() + self.factory = None + return res diff -r 38e912c8bd31 -r cc7a46953471 plcopen/InstancesPathCollector.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plcopen/InstancesPathCollector.py Fri Mar 02 17:01:25 2018 +0100 @@ -0,0 +1,26 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# This file is part of Beremiz. +# See COPYING file for copyrights details. + +from __future__ import absolute_import +from plcopen.XSLTModelQuery import XSLTModelQuery + + +class InstancesPathCollector(XSLTModelQuery): + """ object for collecting instances path list""" + def __init__(self, controller): + self.Instances = [] + XSLTModelQuery.__init__(self, + controller, + "instances_path.xslt", + [("AddInstance", self.AddInstance)]) + + def AddInstance(self, context, *args): + self.Instances.append(args[0][0]) + + def Collect(self, root, name, debug): + self._process_xslt(root, debug, instance_type=name) + res = self.Instances + self.Instances = [] + return res diff -r 38e912c8bd31 -r cc7a46953471 plcopen/Makefile --- a/plcopen/Makefile Mon Feb 19 19:36:43 2018 +0300 +++ b/plcopen/Makefile Fri Mar 02 17:01:25 2018 +0100 @@ -6,10 +6,10 @@ all:$(xsltfiles) -%.xslt: %.ysl2 +%.xslt: %.ysl2 yslt_noindent.yml2 $(yml)/yml2c -I $(yml) $< -o $@.tmp xmlstarlet fo $@.tmp > $@ rm $@.tmp clean: - rm -f $(xsltfiles) \ No newline at end of file + rm -f $(xsltfiles) diff -r 38e912c8bd31 -r cc7a46953471 plcopen/POUVariablesCollector.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plcopen/POUVariablesCollector.py Fri Mar 02 17:01:25 2018 +0100 @@ -0,0 +1,78 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# This file is part of Beremiz. +# See COPYING file for copyrights details. + +from __future__ import absolute_import +from plcopen.XSLTModelQuery import XSLTModelQuery, _StringValue, _BoolValue, _translate_args +from plcopen.types_enums import CLASS_TYPES, POU_TYPES, VAR_CLASS_INFOS + + +def class_extraction(value): + class_type = CLASS_TYPES.get(value) + if class_type is not None: + return class_type + + pou_type = POU_TYPES.get(value) + if pou_type is not None: + return pou_type + + var_type = VAR_CLASS_INFOS.get(value) + if var_type is not None: + return var_type[1] + + return None + + +class _VariablesTreeItemInfos(object): + __slots__ = ["name", "var_class", "type", "edit", "debug", "variables"] + + def __init__(self, *args): + for attr, value in zip(self.__slots__, args): + setattr(self, attr, value if value is not None else "") + + def copy(self): + return _VariablesTreeItemInfos(*[getattr(self, attr) for attr in self.__slots__]) + + +class VariablesTreeInfosFactory(object): + + def __init__(self): + self.Root = None + + def GetRoot(self): + return self.Root + + def SetRoot(self, context, *args): + self.Root = _VariablesTreeItemInfos( + *([''] + _translate_args( + [class_extraction, _StringValue] + [_BoolValue] * 2, + args) + [[]])) + + def AddVariable(self, context, *args): + if self.Root is not None: + self.Root.variables.append(_VariablesTreeItemInfos( + *(_translate_args( + [_StringValue, class_extraction, _StringValue] + + [_BoolValue] * 2, args) + [[]]))) + + +class POUVariablesCollector(XSLTModelQuery): + def __init__(self, controller): + XSLTModelQuery.__init__(self, + controller, + "pou_variables.xslt", + [(name, self.FactoryCaller(name)) + for name in ["SetRoot", "AddVariable"]]) + + def FactoryCaller(self, funcname): + def CallFactory(*args): + return getattr(self.factory, funcname)(*args) + return CallFactory + + def Collect(self, root, debug): + self.factory = VariablesTreeInfosFactory() + self._process_xslt(root, debug) + res = self.factory.GetRoot() + self.factory = None + return res diff -r 38e912c8bd31 -r cc7a46953471 plcopen/VariableInfoCollector.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plcopen/VariableInfoCollector.py Fri Mar 02 17:01:25 2018 +0100 @@ -0,0 +1,87 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# This file is part of Beremiz. +# See COPYING file for copyrights details. + +from __future__ import absolute_import +from plcopen.XSLTModelQuery import XSLTModelQuery, _StringValue, _BoolValue, _translate_args + +# ------------------------------------------------------------------------------- +# Helpers object for generating pou var list +# ------------------------------------------------------------------------------- + + +class _VariableInfos(object): + __slots__ = ["Name", "Class", "Option", "Location", "InitialValue", + "Edit", "Documentation", "Type", "Tree", "Number"] + + def __init__(self, *args): + for attr, value in zip(self.__slots__, args): + setattr(self, attr, value if value is not None else "") + + def copy(self): + return _VariableInfos(*[getattr(self, attr) for attr in self.__slots__]) + + +class VariablesInfosFactory(object): + """ Helpers object for generating pou var list """ + + def __init__(self, variables): + self.Variables = variables + self.TreeStack = [] + self.Type = None + self.Dimensions = None + + def SetType(self, context, *args): + self.Type = args[0][0] + + def GetType(self): + if len(self.Dimensions) > 0: + return ("array", self.Type, self.Dimensions) + return self.Type + + def GetTree(self): + return (self.TreeStack.pop(-1), self.Dimensions) + + def AddDimension(self, context, *args): + self.Dimensions.append(tuple( + _translate_args([_StringValue] * 2, args))) + + def AddTree(self, context, *args): + self.TreeStack.append([]) + self.Dimensions = [] + + def AddVarToTree(self, context, *args): + var = (args[0][0], self.Type, self.GetTree()) + self.TreeStack[-1].append(var) + + def AddVariable(self, context, *args): + self.Variables.append(_VariableInfos(*( + _translate_args([_StringValue] * 5 + [_BoolValue] + [_StringValue], args) + + [self.GetType(), self.GetTree()]))) + + +class VariableInfoCollector(XSLTModelQuery): + def __init__(self, controller): + XSLTModelQuery.__init__(self, + controller, + "variables_infos.xslt", + [(name, self.FactoryCaller(name)) + for name in [ + "SetType", + "AddDimension", + "AddTree", + "AddVarToTree", + "AddVariable"]]) + + def FactoryCaller(self, funcname): + def CallFactory(*args): + return getattr(self.factory, funcname)(*args) + return CallFactory + + def Collect(self, root, debug, variables, tree): + self.factory = VariablesInfosFactory(variables) + self._process_xslt(root, debug, tree=str(tree)) + res = self.factory + self.factory = None + return res diff -r 38e912c8bd31 -r cc7a46953471 plcopen/XSLTModelQuery.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plcopen/XSLTModelQuery.py Fri Mar 02 17:01:25 2018 +0100 @@ -0,0 +1,65 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# This file is part of Beremiz. +# See COPYING file for copyrights details. + +from __future__ import absolute_import +import os +from lxml import etree +import util.paths as paths +from plcopen.structures import StdBlckLibs + +ScriptDirectory = paths.AbsDir(__file__) + + +class XSLTModelQuery(object): + """ a class to handle XSLT queries on project and libs """ + def __init__(self, controller, xsltpath, ext=None): + # arbitrary set debug to false, updated later + self.debug = False + + # merge xslt extensions for library access to query specific ones + xsltext = [ + ("GetProject", lambda *_ignored: + [controller.GetProject(self.debug)]), + ("GetStdLibs", lambda *_ignored: + [lib for lib in StdBlckLibs.values()]), + ("GetExtensions", lambda *_ignored: + [ctn["types"] for ctn in controller.ConfNodeTypes]) + ] + + if ext is not None: + xsltext.extend(ext) + + # parse and compile. "beremiz" arbitrary namespace for extensions + self.xslt = etree.XSLT( + etree.parse( + os.path.join(ScriptDirectory, xsltpath), + etree.XMLParser()), + extensions={("beremiz", name): call for name, call in xsltext}) + + def _process_xslt(self, root, debug, **kwargs): + self.debug = debug + res = self.xslt(root, **{k: etree.XSLT.strparam(v) for k, v in kwargs.iteritems()}) + # print(self.xslt.error_log) + return res + + +# ------------------------------------------------------------------------------- +# Helpers functions for translating list of arguments +# from xslt to valid arguments +# ------------------------------------------------------------------------------- + + +def _StringValue(x): + return x + + +def _BoolValue(x): + return x in ["true", "0"] + + +def _translate_args(translations, args): + return [translate(arg[0]) if len(arg) > 0 else None + for translate, arg in + zip(translations, args)] diff -r 38e912c8bd31 -r cc7a46953471 plcopen/instance_tagname.xslt --- a/plcopen/instance_tagname.xslt Mon Feb 19 19:36:43 2018 +0300 +++ b/plcopen/instance_tagname.xslt Fri Mar 02 17:01:25 2018 +0100 @@ -1,20 +1,12 @@ - + - - - - - - - - - - - + + + + - @@ -26,7 +18,6 @@ - @@ -35,14 +26,12 @@ - - @@ -51,7 +40,6 @@ - @@ -61,7 +49,6 @@ - @@ -75,7 +62,6 @@ - @@ -87,7 +73,6 @@ - @@ -101,18 +86,15 @@ - - - + - @@ -122,16 +104,13 @@ - - - - + @@ -142,42 +121,33 @@ - - - - - - - + - - - @@ -185,7 +155,6 @@ - diff -r 38e912c8bd31 -r cc7a46953471 plcopen/instance_tagname.ysl2 --- a/plcopen/instance_tagname.ysl2 Mon Feb 19 19:36:43 2018 +0300 +++ b/plcopen/instance_tagname.ysl2 Fri Mar 02 17:01:25 2018 +0100 @@ -1,22 +1,19 @@ -include yslt.yml2 -estylesheet xmlns:ppx="http://www.plcopen.org/xml/tc6_0201" +include yslt_noindent.yml2 +istylesheet xmlns:ppx="http://www.plcopen.org/xml/tc6_0201" xmlns:xhtml="http://www.w3.org/1999/xhtml" - xmlns:ns="instance_tagname_ns" + xmlns:ns="beremiz" extension-element-prefixes="ns" exclude-result-prefixes="ns" { param "instance_path"; - variable "project" { - copy "document('project')/project/*"; - } + variable "project", "ns:GetProject()"; - variable "stdlib" { - copy "document('stdlib')/stdlib/*"; - } - variable "extensions" { - copy "document('extensions')/extensions/*"; - } + variable "stdlib", "ns:GetStdLibs()"; + + variable "extensions", "ns:GetExtensions()"; + + variable "all_types", "($project | $stdlib | $extensions)/ppx:types"; function "element_name" { param "path"; @@ -97,12 +94,8 @@ template "ppx:pouInstance" { param "element_path"; variable "type_name" > «@typeName» - apply """exsl:node-set($project)/ppx:project/ppx:types/ppx:pous/ppx:pou[@name=$type_name] | - exsl:node-set($project)/ppx:project/ppx:types/ppx:dataTypes/ppx:dataType[@name=$type_name] | - exsl:node-set($stdlib)/ppx:project/ppx:types/ppx:pous/ppx:pou[@name=$type_name] | - exsl:node-set($stdlib)/ppx:project/ppx:types/ppx:dataTypes/ppx:dataType[@name=$type_name] | - exsl:node-set($extensions)/ppx:project/ppx:types/ppx:pous/ppx:pou[@name=$type_name] | - exsl:node-set($extensions)/ppx:project/ppx:types/ppx:dataTypes/ppx:dataType[@name=$type_name]""" { + apply """$all_types/ppx:pous/ppx:pou[@name=$type_name] | \ + $all_types/ppx:dataTypes/ppx:dataType[@name=$type_name]""" { with "element_path", "$element_path"; } } @@ -150,12 +143,8 @@ template "ppx:derived" { param "element_path"; variable "type_name" > «@name» - apply """exsl:node-set($project)/ppx:project/ppx:types/ppx:pous/ppx:pou[@name=$type_name] | - exsl:node-set($project)/ppx:project/ppx:types/ppx:dataTypes/ppx:dataType[@name=$type_name] | - exsl:node-set($stdlib)/ppx:project/ppx:types/ppx:pous/ppx:pou[@name=$type_name] | - exsl:node-set($stdlib)/ppx:project/ppx:types/ppx:dataTypes/ppx:dataType[@name=$type_name] | - exsl:node-set($extensions)/ppx:project/ppx:types/ppx:pous/ppx:pou[@name=$type_name] | - exsl:node-set($extensions)/ppx:project/ppx:types/ppx:dataTypes/ppx:dataType[@name=$type_name]""" { + apply """$all_types/ppx:pous/ppx:pou[@name=$type_name] | \ + $all_types/ppx:dataTypes/ppx:dataType[@name=$type_name]""" { with "element_path", "$element_path"; } } diff -r 38e912c8bd31 -r cc7a46953471 plcopen/instances_path.xslt --- a/plcopen/instances_path.xslt Mon Feb 19 19:36:43 2018 +0300 +++ b/plcopen/instances_path.xslt Fri Mar 02 17:01:25 2018 +0100 @@ -1,40 +1,23 @@ - + - - - - - - - - - - - - - - + + + + + - - - - - - + - - - @@ -42,14 +25,12 @@ - - @@ -64,8 +45,7 @@ - - + @@ -74,27 +54,22 @@ - - - - - @@ -102,14 +77,12 @@ - - @@ -119,8 +92,7 @@ - - + @@ -129,7 +101,6 @@ - @@ -139,17 +110,14 @@ - - - diff -r 38e912c8bd31 -r cc7a46953471 plcopen/instances_path.ysl2 --- a/plcopen/instances_path.ysl2 Mon Feb 19 19:36:43 2018 +0300 +++ b/plcopen/instances_path.ysl2 Fri Mar 02 17:01:25 2018 +0100 @@ -1,7 +1,7 @@ -include yslt.yml2 -estylesheet xmlns:ppx="http://www.plcopen.org/xml/tc6_0201" +include yslt_noindent.yml2 +istylesheet xmlns:ppx="http://www.plcopen.org/xml/tc6_0201" xmlns:xhtml="http://www.w3.org/1999/xhtml" - xmlns:ns="instances_ns" + xmlns:ns="beremiz" extension-element-prefixes="ns" exclude-result-prefixes="ns" { @@ -9,21 +9,16 @@ template "text()"; - variable "project" { - copy "document('project')/project/*"; - } + variable "project", "ns:GetProject()"; - variable "stdlib" { - copy "document('stdlib')/stdlib/*"; - } - variable "extensions" { - copy "document('extensions')/extensions/*"; - } - + variable "stdlib", "ns:GetStdLibs()"; + + variable "extensions", "ns:GetExtensions()"; + + variable "all_types", "($project | $stdlib | $extensions)/ppx:types"; + template "ppx:project" { - instances { - apply "ppx:instances/ppx:configurations/ppx:configuration"; - } + apply "ppx:instances/ppx:configurations/ppx:configuration"; } template "ppx:configuration" { @@ -49,12 +44,8 @@ } otherwise { variable "type_name" > «@typeName» - apply """exsl:node-set($project)/ppx:project/ppx:types/ppx:pous/ppx:pou[@name=$type_name] | - exsl:node-set($project)/ppx:project/ppx:types/ppx:dataTypes/ppx:dataType[@name=$type_name] | - exsl:node-set($stdlib)/ppx:project/ppx:types/ppx:pous/ppx:pou[@name=$type_name] | - exsl:node-set($stdlib)/ppx:project/ppx:types/ppx:dataTypes/ppx:dataType[@name=$type_name] | - exsl:node-set($extensions)/ppx:project/ppx:types/ppx:pous/ppx:pou[@name=$type_name] | - exsl:node-set($extensions)/ppx:project/ppx:types/ppx:dataTypes/ppx:dataType[@name=$type_name]""" { + apply """$all_types/ppx:pous/ppx:pou[@name=$type_name] | \ + $all_types/ppx:dataTypes/ppx:dataType[@name=$type_name]""" { with "instance_path" > «$pou_instance_path» } } @@ -91,12 +82,8 @@ } otherwise { variable "type_name" > «@name» - apply """exsl:node-set($project)/ppx:project/ppx:types/ppx:pous/ppx:pou[@name=$type_name] | - exsl:node-set($project)/ppx:project/ppx:types/ppx:dataTypes/ppx:dataType[@name=$type_name] | - exsl:node-set($stdlib)/ppx:project/ppx:types/ppx:pous/ppx:pou[@name=$type_name] | - exsl:node-set($stdlib)/ppx:project/ppx:types/ppx:dataTypes/ppx:dataType[@name=$type_name] | - exsl:node-set($extensions)/ppx:project/ppx:types/ppx:pous/ppx:pou[@name=$type_name] | - exsl:node-set($extensions)/ppx:project/ppx:types/ppx:dataTypes/ppx:dataType[@name=$type_name]""" { + apply """$all_types/ppx:pous/ppx:pou[@name=$type_name] | \ + $all_types/ppx:dataTypes/ppx:dataType[@name=$type_name]""" { with "instance_path" > «$variable_path» } } diff -r 38e912c8bd31 -r cc7a46953471 plcopen/pou_block_instances.xslt --- a/plcopen/pou_block_instances.xslt Mon Feb 19 19:36:43 2018 +0300 +++ b/plcopen/pou_block_instances.xslt Fri Mar 02 17:01:25 2018 +0100 @@ -1,24 +1,15 @@ - + - - - - - + - - - - + - - @@ -29,7 +20,6 @@ - @@ -37,18 +27,13 @@ - - - - - + - @@ -66,12 +51,9 @@ - - - + - @@ -91,7 +73,6 @@ - output @@ -99,7 +80,6 @@ - @@ -108,7 +88,6 @@ - @@ -120,7 +99,6 @@ - @@ -128,7 +106,6 @@ - @@ -136,23 +113,18 @@ - - STRING - WSTRING - - @@ -163,14 +135,10 @@ BOOL - - - - - - - - + + + + @@ -184,51 +152,40 @@ - - - - - - - - - - - - + + - @@ -248,19 +205,14 @@ - - - + - - - + - @@ -278,18 +230,12 @@ - - - - - - + + - - @@ -297,15 +243,10 @@ - - - - - - + + - @@ -341,7 +282,6 @@ - @@ -349,15 +289,10 @@ - - - - - - + + - @@ -377,15 +312,10 @@ - - - - - - + + - jump @@ -395,12 +325,9 @@ - - - + - @@ -434,18 +361,14 @@ - - - - + - diff -r 38e912c8bd31 -r cc7a46953471 plcopen/pou_block_instances.ysl2 --- a/plcopen/pou_block_instances.ysl2 Mon Feb 19 19:36:43 2018 +0300 +++ b/plcopen/pou_block_instances.ysl2 Fri Mar 02 17:01:25 2018 +0100 @@ -1,7 +1,7 @@ -include yslt.yml2 -estylesheet xmlns:ppx="http://www.plcopen.org/xml/tc6_0201" +include yslt_noindent.yml2 +istylesheet xmlns:ppx="http://www.plcopen.org/xml/tc6_0201" xmlns:xhtml="http://www.w3.org/1999/xhtml" - xmlns:ns="pou_block_instances_ns" + xmlns:ns="beremiz" extension-element-prefixes="ns" exclude-result-prefixes="ns" { @@ -312,4 +312,4 @@ with "negated", "@negated"; } } -} \ No newline at end of file +} diff -r 38e912c8bd31 -r cc7a46953471 plcopen/pou_variables.xslt --- a/plcopen/pou_variables.xslt Mon Feb 19 19:36:43 2018 +0300 +++ b/plcopen/pou_variables.xslt Fri Mar 02 17:01:25 2018 +0100 @@ -1,34 +1,16 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + - @@ -40,7 +22,6 @@ - @@ -49,37 +30,26 @@ - - - - - - + + - action - - - + - transition - - - + - configuration @@ -88,15 +58,10 @@ false - - - - - - + + - resource @@ -105,45 +70,32 @@ false - - - - - - + + - - - - - + - - - + - - - + - Local @@ -151,7 +103,6 @@ - Global @@ -159,7 +110,6 @@ - External @@ -167,7 +117,6 @@ - Temp @@ -175,7 +124,6 @@ - Input @@ -183,7 +131,6 @@ - Output @@ -191,7 +138,6 @@ - InOut @@ -199,7 +145,6 @@ - @@ -212,7 +157,6 @@ - @@ -223,7 +167,6 @@ - @@ -234,7 +177,6 @@ - @@ -248,7 +190,6 @@ - @@ -262,17 +203,12 @@ - - - - + - - - - + + @@ -280,20 +216,16 @@ - - - - ARRAY [ @@ -301,30 +233,22 @@ ] OF - - - + - STRING - WSTRING - - - - - + - + true @@ -333,26 +257,17 @@ - - - - + - false - - - - + - - - - + + false @@ -360,19 +275,15 @@ - true - false - false - true diff -r 38e912c8bd31 -r cc7a46953471 plcopen/pou_variables.ysl2 --- a/plcopen/pou_variables.ysl2 Mon Feb 19 19:36:43 2018 +0300 +++ b/plcopen/pou_variables.ysl2 Fri Mar 02 17:01:25 2018 +0100 @@ -1,7 +1,7 @@ -include yslt.yml2 -estylesheet xmlns:ppx="http://www.plcopen.org/xml/tc6_0201" +include yslt_noindent.yml2 +istylesheet xmlns:ppx="http://www.plcopen.org/xml/tc6_0201" xmlns:xhtml="http://www.w3.org/1999/xhtml" - xmlns:ns="pou_vars_ns" + xmlns:ns="beremiz" extension-element-prefixes="ns" exclude-result-prefixes="ns" { @@ -11,16 +11,13 @@ template "text()", mode="var_edit"; template "text()", mode="var_debug"; - variable "project" { - copy "document('project')/project/*"; - } - - variable "stdlib" { - copy "document('stdlib')/stdlib/*"; - } - variable "extensions" { - copy "document('extensions')/extensions/*"; - } + variable "project", "ns:GetProject()"; + + variable "stdlib", "ns:GetStdLibs()"; + + variable "extensions", "ns:GetExtensions()"; + + variable "all_types", "($project | $stdlib | $extensions)/ppx:types"; function "add_root" { param "class"; @@ -176,14 +173,10 @@ template "*[self::ppx:type or self::ppx:baseType]/ppx:derived", mode="var_class" { param "default_class"; variable "type_name", "@name"; - variable "pou_infos" { - copy """exsl:node-set($project)/ppx:project/ppx:types/ppx:pous/ppx:pou[@name=$type_name] | - exsl:node-set($stdlib)/ppx:project/ppx:types/ppx:pous/ppx:pou[@name=$type_name] | - exsl:node-set($extensions)/ppx:project/ppx:types/ppx:pous/ppx:pou[@name=$type_name]"""; - } + variable "pou_infos", "$all_types/ppx:pous/ppx:pou[@name=$type_name]"; choose { - when "$pou_infos != ''" { - apply "exsl:node-set($pou_infos)", mode="var_class"; + when "$pou_infos" { + apply "$pou_infos", mode="var_class"; } otherwise { value "$default_class" @@ -227,11 +220,9 @@ template "*[self::ppx:type or self::ppx:baseType]/ppx:derived", mode="var_edit" { variable "type_name", "@name"; - variable "pou_infos" { - copy "exsl:node-set($project)/ppx:project/ppx:types/ppx:pous/ppx:pou[@name=$type_name]"; - } + variable "pou_infos", "$project/ppx:types/ppx:pous/ppx:pou[@name=$type_name]"; choose { - when "$pou_infos != ''" > true + when "$pou_infos" > true otherwise > false } } @@ -246,15 +237,13 @@ template "*[self::ppx:type or self::ppx:baseType]/ppx:derived", mode="var_debug" { variable "type_name", "@name"; - variable "datatype_infos" { - copy """exsl:node-set($project)/ppx:project/ppx:types/ppx:pous/ppx:pou[@name=$type_name] | - exsl:node-set($project)/ppx:project/ppx:types/ppx:dataTypes/ppx:dataType[@name=$type_name] | - exsl:node-set($stdlib)/ppx:project/ppx:types/ppx:dataTypes/ppx:dataType[@name=$type_name] | - exsl:node-set($extensions)/ppx:project/ppx:types/ppx:dataTypes/ppx:dataType[@name=$type_name]"""; - } + variable "datatype_infos", """ \ + $project/ppx:types/ppx:pous/ppx:pou[@name=$type_name] | \ + $all_types/ppx:dataTypes/ppx:dataType[@name=$type_name] \ + """; choose { - when "$datatype_infos != ''" { - apply "exsl:node-set($datatype_infos)", mode="var_debug"; + when "$datatype_infos" { + apply "$datatype_infos", mode="var_debug"; } otherwise > false } diff -r 38e912c8bd31 -r cc7a46953471 plcopen/types_enums.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plcopen/types_enums.py Fri Mar 02 17:01:25 2018 +0100 @@ -0,0 +1,131 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# This file is part of Beremiz +# See COPYING file for copyrights details. + +from __future__ import absolute_import +from util.TranslationCatalogs import NoTranslate +_ = NoTranslate + +ITEMS_EDITABLE = [ + ITEM_PROJECT, + ITEM_POU, + ITEM_VARIABLE, + ITEM_TRANSITION, + ITEM_ACTION, + ITEM_CONFIGURATION, + ITEM_RESOURCE, + ITEM_DATATYPE +] = range(8) + +ITEMS_UNEDITABLE = [ + ITEM_DATATYPES, + ITEM_FUNCTION, + ITEM_FUNCTIONBLOCK, + ITEM_PROGRAM, + ITEM_TRANSITIONS, + ITEM_ACTIONS, + ITEM_CONFIGURATIONS, + ITEM_RESOURCES, + ITEM_PROPERTIES +] = range(8, 17) + +ITEMS_VARIABLE = [ + ITEM_VAR_LOCAL, + ITEM_VAR_GLOBAL, + ITEM_VAR_EXTERNAL, + ITEM_VAR_TEMP, + ITEM_VAR_INPUT, + ITEM_VAR_OUTPUT, + ITEM_VAR_INOUT +] = range(17, 24) + +ITEM_CONFNODE = 25 + +VAR_CLASS_INFOS = { + "Local": ("localVars", ITEM_VAR_LOCAL), + "Global": ("globalVars", ITEM_VAR_GLOBAL), + "External": ("externalVars", ITEM_VAR_EXTERNAL), + "Temp": ("tempVars", ITEM_VAR_TEMP), + "Input": ("inputVars", ITEM_VAR_INPUT), + "Output": ("outputVars", ITEM_VAR_OUTPUT), + "InOut": ("inOutVars", ITEM_VAR_INOUT)} + +POU_TYPES = { + "program": ITEM_PROGRAM, + "functionBlock": ITEM_FUNCTIONBLOCK, + "function": ITEM_FUNCTION, +} + +CLASS_TYPES = { + "configuration": ITEM_CONFIGURATION, + "resource": ITEM_RESOURCE, + "action": ITEM_ACTION, + "transition": ITEM_TRANSITION, + "program": ITEM_PROGRAM +} + +LOCATIONS_ITEMS = [LOCATION_CONFNODE, + LOCATION_MODULE, + LOCATION_GROUP, + LOCATION_VAR_INPUT, + LOCATION_VAR_OUTPUT, + LOCATION_VAR_MEMORY] = range(6) + +UNEDITABLE_NAMES = [_("User-defined POUs"), _("Functions"), _("Function Blocks"), + _("Programs"), _("Data Types"), _("Transitions"), _("Actions"), + _("Configurations"), _("Resources"), _("Properties")] + +[USER_DEFINED_POUS, FUNCTIONS, FUNCTION_BLOCKS, PROGRAMS, + DATA_TYPES, TRANSITIONS, ACTIONS, CONFIGURATIONS, + RESOURCES, PROPERTIES] = UNEDITABLE_NAMES + +# ------------------------------------------------------------------------------- +# Project Element tag name computation functions +# ------------------------------------------------------------------------------- + + +# Compute a data type name +def ComputeDataTypeName(datatype): + return "D::%s" % datatype + + +# Compute a pou name +def ComputePouName(pou): + return "P::%s" % pou + + +# Compute a pou transition name +def ComputePouTransitionName(pou, transition): + return "T::%s::%s" % (pou, transition) + + +# Compute a pou action name +def ComputePouActionName(pou, action): + return "A::%s::%s" % (pou, action) + + +# Compute a pou name +def ComputeConfigurationName(config): + return "C::%s" % config + + +# Compute a pou name +def ComputeConfigurationResourceName(config, resource): + return "R::%s::%s" % (config, resource) + + +def GetElementType(tagname): + words = tagname.split("::") + return { + "D": ITEM_DATATYPE, + "P": ITEM_POU, + "T": ITEM_TRANSITION, + "A": ITEM_ACTION, + "C": ITEM_CONFIGURATION, + "R": ITEM_RESOURCE + }[words[0]] + + +# remove gettext override +del _ diff -r 38e912c8bd31 -r cc7a46953471 plcopen/variables_infos.xslt --- a/plcopen/variables_infos.xslt Mon Feb 19 19:36:43 2018 +0300 +++ b/plcopen/variables_infos.xslt Fri Mar 02 17:01:25 2018 +0100 @@ -1,48 +1,26 @@ - + - - - - - - - - - - - - - - + + + + + - - - - + - - - - + - - - - + - - - - + - @@ -59,9 +37,7 @@ - - - + @@ -69,21 +45,16 @@ true - - - + - - - + - Local @@ -91,7 +62,6 @@ - Global @@ -99,7 +69,6 @@ - External @@ -107,7 +76,6 @@ - Temp @@ -115,7 +83,6 @@ - Input @@ -123,7 +90,6 @@ - Output @@ -131,7 +97,6 @@ - InOut @@ -139,53 +104,35 @@ - - - - + - - - - + - - - - + - - - - + - - - - + - - - - + @@ -197,34 +144,28 @@ - STRING - WSTRING - - - - - + false @@ -235,47 +176,35 @@ - true - ( - - - + ) := - - - + - - - + - - - - - + , @@ -285,7 +214,6 @@ - [ @@ -296,7 +224,6 @@ - ( diff -r 38e912c8bd31 -r cc7a46953471 plcopen/variables_infos.ysl2 --- a/plcopen/variables_infos.ysl2 Mon Feb 19 19:36:43 2018 +0300 +++ b/plcopen/variables_infos.ysl2 Fri Mar 02 17:01:25 2018 +0100 @@ -1,7 +1,7 @@ -include yslt.yml2 -estylesheet xmlns:ppx="http://www.plcopen.org/xml/tc6_0201" +include yslt_noindent.yml2 +istylesheet xmlns:ppx="http://www.plcopen.org/xml/tc6_0201" xmlns:xhtml="http://www.w3.org/1999/xhtml" - xmlns:ns="var_infos_ns" + xmlns:ns="beremiz" extension-element-prefixes="ns" exclude-result-prefixes="ns" { @@ -9,17 +9,14 @@ template "text()"; - variable "project" { - copy "document('project')/project/*"; - } - - variable "stdlib" { - copy "document('stdlib')/stdlib/*"; - } - variable "extensions" { - copy "document('extensions')/extensions/*"; - } - + variable "project", "ns:GetProject()"; + + variable "stdlib", "ns:GetStdLibs()"; + + variable "extensions", "ns:GetExtensions()"; + + variable "all_types", "($project | $stdlib | $extensions)/ppx:types"; + template "ppx:configuration" { apply "ppx:globalVars"; } @@ -129,12 +126,8 @@ variable "type_name" > «@name» choose { when "$tree='True'" { - apply """exsl:node-set($project)/ppx:project/ppx:types/ppx:pous/ppx:pou[@name=$type_name] | - exsl:node-set($project)/ppx:project/ppx:types/ppx:dataTypes/ppx:dataType[@name=$type_name] | - exsl:node-set($stdlib)/ppx:project/ppx:types/ppx:pous/ppx:pou[@name=$type_name] | - exsl:node-set($stdlib)/ppx:project/ppx:types/ppx:dataTypes/ppx:dataType[@name=$type_name] | - exsl:node-set($extensions)/ppx:project/ppx:types/ppx:pous/ppx:pou[@name=$type_name] | - exsl:node-set($extensions)/ppx:project/ppx:types/ppx:dataTypes/ppx:dataType[@name=$type_name]""", mode="var_type"; + apply """$all_types/ppx:pous/ppx:pou[@name=$type_name] | \ + $all_types/ppx:dataTypes/ppx:dataType[@name=$type_name]""", mode="var_type"; } } value "ns:SetType($type_name)"; @@ -166,11 +159,7 @@ template "*[self::ppx:type or self::ppx:baseType or self::ppx:returnType]/ppx:derived", mode="var_edit" { variable "type_name" > «@name» - variable "pou_infos" { - copy """exsl:node-set($project)/ppx:project/ppx:types/ppx:pous/ppx:pou[@name=$type_name] | - exsl:node-set($stdlib)/ppx:project/ppx:types/ppx:pous/ppx:pou[@name=$type_name] | - exsl:node-set($extensions)/ppx:project/ppx:types/ppx:pous/ppx:pou[@name=$type_name]"""; - } + variable "pou_infos", "$all_types/ppx:pous/ppx:pou[@name=$type_name]"; choose { when "$pou_infos != ''" > false otherwise > true @@ -232,4 +221,4 @@ } - \ No newline at end of file + diff -r 38e912c8bd31 -r cc7a46953471 plcopen/yslt_noindent.yml2 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plcopen/yslt_noindent.yml2 Fri Mar 02 17:01:25 2018 +0100 @@ -0,0 +1,32 @@ +include yslt.yml2 + +in xsl { + decl istylesheet ( + *output="xml", + version="1.0", + xmlns:xsl="http://www.w3.org/1999/XSL/Transform", + xmlns:exsl='http://exslt.org/common', + extension-element-prefixes='exsl' + ) alias stylesheet { + output *output; + content; + }; + + decl template(match) { + content; + }; + + decl function(name) alias template { + content; + }; + + decl call(name) alias call-template { + content; + }; + + decl apply(select) alias apply-templates { + content; + }; +} + + diff -r 38e912c8bd31 -r cc7a46953471 py_ext/PythonFileCTNMixin.py --- a/py_ext/PythonFileCTNMixin.py Mon Feb 19 19:36:43 2018 +0300 +++ b/py_ext/PythonFileCTNMixin.py Fri Mar 02 17:01:25 2018 +0100 @@ -119,6 +119,7 @@ "configname": configname.upper(), "uppername": variable.getname().upper(), "IECtype": variable.gettype(), + "initial": repr(variable.getinitial()), "pyextname": pyextname }, self.CodeFile.variables.variable) @@ -136,6 +137,7 @@ _%(pyextname)sGlobalsDesc.append(( "%(name)s", "%(IECtype)s", + %(initial)s, %(desc)s, %(onchange)s, %(opts)s)) @@ -169,7 +171,7 @@ ## ## Code for PLC global variable access -from targets.typemapping import TypeTranslator +from runtime.typemapping import TypeTranslator import ctypes _%(pyextname)sGlobalsDesc = [] __ext_name__ = "%(pyextname)s" diff -r 38e912c8bd31 -r cc7a46953471 runtime/NevowServer.py --- a/runtime/NevowServer.py Mon Feb 19 19:36:43 2018 +0300 +++ b/runtime/NevowServer.py Fri Mar 02 17:01:25 2018 +0100 @@ -26,10 +26,10 @@ from __future__ import absolute_import from __future__ import print_function import os -import util.paths as paths from nevow import appserver, inevow, tags, loaders, athena from nevow.page import renderer from twisted.internet import reactor +import util.paths as paths xhtml_header = ''' - + diff -r 38e912c8bd31 -r cc7a46953471 util/ExceptionHandler.py --- a/util/ExceptionHandler.py Mon Feb 19 19:36:43 2018 +0300 +++ b/util/ExceptionHandler.py Fri Mar 02 17:01:25 2018 +0100 @@ -37,7 +37,7 @@ Max_Traceback_List_Size = 20 -def Display_Exception_Dialog(e_type, e_value, e_tb, bug_report_path): +def Display_Exception_Dialog(e_type, e_value, e_tb, bug_report_path, exit): trcbck_lst = [] for i, line in enumerate(traceback.extract_tb(e_tb)): trcbck = " " + str(i+1) + ". " @@ -74,6 +74,9 @@ finally: dlg.Destroy() + if exit: + sys.exit() # wx.Exit() + return res @@ -125,7 +128,7 @@ output.write(a + ":\n" + str(info[a]) + "\n\n") output.close() - def handle_exception(e_type, e_value, e_traceback): + def handle_exception(e_type, e_value, e_traceback, exit=False): traceback.print_exception(e_type, e_value, e_traceback) # this is very helpful when there's an exception in the rest of this func last_tb = get_last_traceback(e_traceback) ex = (last_tb.tb_frame.f_code.co_filename, last_tb.tb_frame.f_lineno) @@ -135,7 +138,7 @@ path = tempfile.gettempdir()+os.sep+wx.GetApp().GetAppName() bug_report_path = path + os.sep + "bug_report_" + time.strftime("%Y_%m_%d__%H-%M-%S") + ".txt" save_bug_report(e_type, e_value, e_traceback, bug_report_path, date) - Display_Exception_Dialog(e_type, e_value, e_traceback, bug_report_path) + wx.CallAfter(Display_Exception_Dialog, e_type, e_value, e_traceback, bug_report_path, exit) # sys.excepthook = lambda *args: wx.CallAfter(handle_exception, *args) sys.excepthook = handle_exception @@ -154,3 +157,5 @@ sys.excepthook(*sys.exc_info()) self.run = run_with_except_hook threading.Thread.__init__ = init + + return handle_exception diff -r 38e912c8bd31 -r cc7a46953471 util/ProcessLogger.py --- a/util/ProcessLogger.py Mon Feb 19 19:36:43 2018 +0300 +++ b/util/ProcessLogger.py Fri Mar 02 17:01:25 2018 +0100 @@ -29,9 +29,7 @@ import subprocess import ctypes from threading import Timer, Lock, Thread, Semaphore -import wx -if os.name == 'posix': - from signal import SIGTERM, SIGKILL +import signal class outputThread(Thread): @@ -126,11 +124,11 @@ "stderr": subprocess.PIPE } - if no_gui and wx.Platform == '__WXMSW__': + if no_gui and os.name in ("nt", "ce"): self.startupinfo = subprocess.STARTUPINFO() self.startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW popenargs["startupinfo"] = self.startupinfo - elif wx.Platform == '__WXGTK__': + elif os.name == 'posix': popenargs["shell"] = False if timeout: @@ -200,16 +198,16 @@ self.outt.killed = True self.errt.killed = True - if wx.Platform == '__WXMSW__': + if os.name in ("nt", "ce"): PROCESS_TERMINATE = 1 handle = ctypes.windll.kernel32.OpenProcess(PROCESS_TERMINATE, False, self.Proc.pid) ctypes.windll.kernel32.TerminateProcess(handle, -1) ctypes.windll.kernel32.CloseHandle(handle) else: if gently: - sig = SIGTERM + sig = signal.SIGTERM else: - sig = SIGKILL + sig = signal.SIGKILL try: os.kill(self.Proc.pid, sig) except Exception: diff -r 38e912c8bd31 -r cc7a46953471 util/misc.py --- a/util/misc.py Mon Feb 19 19:36:43 2018 +0300 +++ b/util/misc.py Fri Mar 02 17:01:25 2018 +0100 @@ -47,14 +47,21 @@ return True -def GetClassImporter(classpath): - if isinstance(classpath, str): - def fac(): - mod = __import__(classpath.rsplit('.', 1)[0]) - return reduce(getattr, classpath.split('.')[1:], mod) - return fac +def GetClassImporter(param): + """ + is used to resolve library class names in features.py + if param is a string, returns a callable that return the class pointed by param + if a class is given, then returns a callable that returns the given class. + """ + + if isinstance(param, str): + def factory(): + # on-demand import, only when using class + mod = __import__(param.rsplit('.', 1)[0]) + return reduce(getattr, param.split('.')[1:], mod) + return factory else: - return classpath + return lambda: param def InstallLocalRessources(CWD):