--- a/Beremiz.py Tue Sep 04 17:16:42 2007 +0200
+++ b/Beremiz.py Tue Sep 04 17:58:29 2007 +0200
@@ -26,22 +26,10 @@
-from time import localtime
-from datetime import datetime
import os, re, platform, sys, time, traceback, getopt, commands
-base_folder = os.path.split(sys.path[0])[0]
-sys.path.append(os.path.join(base_folder, "plcopeneditor"))
-sys.path.append(os.path.join(base_folder, "CanFestival-3", "objdictgen"))
-sys.path.append(os.path.join(base_folder, "wxsvg", "svgui", "defeditor"))
-iec2cc_path = os.path.join(base_folder, "matiec", "iec2cc")
-ieclib_path = os.path.join(base_folder, "matiec", "lib")
-from PLCOpenEditor import PLCOpenEditor, ProjectDialog
-from TextViewer import TextViewer
-from plcopen.structures import IEC_KEYWORDS#, AddPlugin
from plugger import PluginsRoot
@@ -80,6 +68,41 @@
+ def LogCommand(self, Command, sz_limit = 100): + import os, popen2, fcntl, select, signal + child = popen2.Popen3(Command, 1) # capture stdout and stderr from command + child.tochild.close() # don't need to talk to child + outfile = child.fromchild + outfd = outfile.fileno() + errfile = child.childerr + errfd = errfile.fileno() + ready = select.select([outfd,errfd],[],[]) # wait for input + outchunk = outfile.readline() + if outchunk == '': outeof = 1 + errchunk = errfile.readline() + if errchunk == '': erreof = 1 + self.write_warning(errchunk) + if outeof and erreof : break + if errlen > sz_limit or outlen > sz_limit : + os.kill(child.pid, signal.SIGTERM) + self.write_error("Output size reached limit -- killed\n") + return (err, outdata, errdata) [ID_BEREMIZ, ID_BEREMIZMAINSPLITTER,
ID_BEREMIZSECONDSPLITTER, ID_BEREMIZLEFTPANEL,
ID_BEREMIZPARAMSPANEL, ID_BEREMIZLOGCONSOLE,
@@ -299,14 +322,12 @@
self.Log = LogPseudoFile(self.LogConsole)
- self.PluginRoot = PluginsRoot()
+ self.PluginRoot = PluginsRoot(self) self.PluginRoot.LoadProject(projectOpen)
self.RefreshPluginParams()
@@ -588,123 +609,6 @@
textctrl.SetValue(str(element_infos["value"]))
- def UpdateAttributesTreeParts(self, tree, new_tree):
- tree_leafs = [(element_infos["name"], element_infos["type"]) for element_infos in tree["children"]]
- new_tree_leafs = [(element_infos["name"], element_infos["type"]) for element_infos in new_tree["children"]]
- if tree_leafs != new_tree_leafs:
- tree["children"] = new_tree["children"]
- for child in tree["children"]:
- self.PrepareAttributesTree(child)
- for idx, new_element_infos in enumerate(new_tree["children"]):
- tree["children"][idx]["value"] = new_element_infos["value"]
- if len(new_element_infos["children"]) > 0:
- self.UpdateAttributesTreeParts(tree["children"][idx], new_element_infos)
- def PrepareAttributesTree(self, tree):
- if len(tree["children"]) > 0:
- for child in tree["children"]:
- self.PrepareAttributesTree(child)
- def GenerateTable(self, data, tree, path, indent):
- tree_path = "%s.%s"%(path, tree["name"])
- infos = {"Attribute" : " " * indent + tree["name"], "Value" : tree["value"], "Type" : tree["type"], "Open" : "", "Path" : tree_path}
- tree_path = tree["name"]
- if len(tree["children"]) > 0:
- if tree["open"] or not path:
- for child in tree["children"]:
- self.GenerateTable(data, child, tree_path, indent)
- def RefreshAttributesGrid(self):
- plugin = self.GetSelectedPlugin()
- self.AttributesTree = []
- new_params = plugin.GetParamsAttributes()
- for idx, child in enumerate(new_params):
- if len(self.AttributesTree) > idx:
- if self.AttributesTree[idx]["name"] == child["name"]:
- self.UpdateAttributesTreeParts(self.AttributesTree[idx], child)
- self.AttributesTree[idx] = child
- self.PrepareAttributesTree(child)
- self.AttributesTree.append(child)
- self.PrepareAttributesTree(child)
- while len(self.AttributesTree) > len(new_params):
- self.AttributesTree.pop(-1)
- for child in self.AttributesTree:
- self.GenerateTable(data, child, None, 0)
- self.Table.SetData(data)
- self.Table.ResetView(self.AttributesGrid)
- def OpenClose(self, tree, path):
- parts = path.split(".", 1)
- for child in tree["children"]:
- if child["name"] == parts[0]:
- return self.OpenClose(child, parts[1])
- elif len(child["children"]) > 0:
- child["open"] = not child["open"]
- def OpenCloseAttribute(self):
- if self.AttributesGrid.GetGridCursorCol() == 0:
- row = self.AttributesGrid.GetGridCursorRow()
- path = self.Table.GetValueByName(row, "Path")
- parts = path.split(".", 1)
- for child in self.AttributesTree:
- if child["name"] == parts[0] and len(parts) > 1:
- result = self.OpenClose(child, parts[1])
- self.RefreshAttributesGrid()
- def OnParamsEnableChanged(self, event):
- plugin = self.GetSelectedPlugin()
- if plugin and plugin != self.PluginRoot:
- plugin.BaseParams.setEnabled(event.Checked())
- def OnParamsIECChannelChanged(self, event):
- plugin = self.GetSelectedPlugin()
- if plugin and plugin != self.PluginRoot:
- plugin.BaseParams.setIEC_Channel(self.ParamsIECChannel.GetValue())
- def OnParamsTargetTypeChanged(self, event):
- plugin = self.GetSelectedPlugin()
- if plugin and plugin == self.PluginRoot:
- self.PluginRoot.ChangeTargetType(self.ParamsTargetType.GetStringSelection())
- self.RefreshAttributesGrid()
- def OnAttributesGridCellChange(self, event):
- plugin = self.GetSelectedPlugin()
- path = self.Table.GetValueByName(row, "Path")
- value = self.Table.GetValueByName(row, "Value")
- plugin.SetParamsAttribute(path, value)
- print plugin.GetParamsAttributes(path)
- self.RefreshAttributesGrid()
- def OnAttributesGridCellLeftClick(self, event):
- wx.CallAfter(self.OpenCloseAttribute)
def OnNewProjectMenu(self, event):
defaultpath = self.PluginRoot.GetProjectPath()
@@ -713,25 +617,20 @@
if dialog.ShowModal() == wx.ID_OK:
projectpath = dialog.GetPath()
- if os.path.isdir(projectpath) and len(os.listdir(projectpath)) == 0:
- dialog = ProjectDialog(self)
- if dialog.ShowModal() == wx.ID_OK:
- values = dialog.GetValues()
- values["creationDateTime"] = datetime(*localtime()[:6])
- self.PluginRoot.NewProject(projectpath, values)
- self.RefreshPluginTree()
+ res = self.PluginRoot.NewProject(projectpath) + self.RefreshPluginTree() - message = wx.MessageDialog(self, "Folder choosen isn't empty. You can't use it for a new project!", "ERROR", wx.OK|wx.ICON_ERROR)
+ message = wx.MessageDialog(self, res, "ERROR", wx.OK|wx.ICON_ERROR) def OnOpenProjectMenu(self, event):
defaultpath = self.PluginRoot.GetProjectPath()
defaultpath = os.getcwd()
dialog = wx.DirDialog(self , "Choose a project", defaultpath, wx.DD_NEW_DIR_BUTTON)
if dialog.ShowModal() == wx.ID_OK:
@@ -785,7 +684,7 @@
def OnBuildMenu(self, event):
def OnSimulateMenu(self, event):
@@ -828,50 +727,6 @@
- self.PLCEditor = PLCOpenEditor(self, self.PluginRoot.PLCManager)
- self.PLCEditor.RefreshProjectTree()
- self.PLCEditor.RefreshFileMenu()
- self.PLCEditor.RefreshEditMenu()
- self.PLCEditor.RefreshToolBar()
- def LogCommand(self, Command, sz_limit = 100):
- import os, popen2, fcntl, select, signal
- child = popen2.Popen3(Command, 1) # capture stdout and stderr from command
- child.tochild.close() # don't need to talk to child
- outfile = child.fromchild
- outfd = outfile.fileno()
- errfile = child.childerr
- errfd = errfile.fileno()
- ready = select.select([outfd,errfd],[],[]) # wait for input
- outchunk = outfile.readline()
- if outchunk == '': outeof = 1
- self.Log.write(outchunk)
- errchunk = errfile.readline()
- if errchunk == '': erreof = 1
- self.Log.write_warning(errchunk)
- if outeof and erreof : break
- if errlen > sz_limit or outlen > sz_limit :
- os.kill(child.pid, signal.SIGTERM)
- self.Log.write_error("Output size reached limit -- killed\n")
- return (err, outdata, errdata)
#-------------------------------------------------------------------------------
--- a/plugger.py Tue Sep 04 17:16:42 2007 +0200
+++ b/plugger.py Tue Sep 04 17:58:29 2007 +0200
@@ -2,14 +2,17 @@
Base definitions for beremiz plugins
from xml.dom import minidom
-from xmlclass import GenerateClassesFromXSDstring
-from PLCControler import PLCControler
+#Quick hack to be able to find Beremiz IEC tools. Should be config params. +base_folder = os.path.split(sys.path[0])[0] +sys.path.append(os.path.join(base_folder, "plcopeneditor")) +from xmlclass import GenerateClassesFromXSDstring _BaseParamsClass = GenerateClassesFromXSDstring("""<?xml version="1.0" encoding="ISO-8859-1" ?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
@@ -66,6 +69,7 @@
+ #Default, do nothing and return success def GetParamsAttributes(self, path = None):
@@ -98,17 +102,15 @@
# generate XML for base XML parameters controller of the plugin
- basexmlfilepath = self.PluginBaseXmlFilePath()
- BaseXMLFile = open(basexmlfilepath,'w')
+ if self.MandatoryParams: + BaseXMLFile = open(self.PluginBaseXmlFilePath(),'w') BaseXMLFile.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
BaseXMLFile.write(self.MandatoryParams[1].generateXMLText(self.MandatoryParams[0], 0))
# generate XML for XML parameters controller of the plugin
- xmlfilepath = self.PluginXmlFilePath()
- XMLFile = open(xmlfilepath,'w')
+ XMLFile = open(self.PluginXmlFilePath(),'w') XMLFile.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
XMLFile.write(self.PlugParams[1].generateXMLText(self.PlugParams[0], 0))
@@ -292,8 +294,14 @@
if os.path.isdir(_self.PlugPath(PlugName)) and os.path.isfile(_self.PluginXmlFilePath(PlugName)):
#Load the plugin.xml file into parameters members
_self.LoadXMLParams(PlugName)
+ # Basic check. Better to fail immediately. + if (_self.BaseParams.getName() != PlugName): + raise Exception, "Project tree layout do not match plugin.xml %s!=%s "%(PlugName, _self.BaseParams.getName()) + # Now, self.PlugPath() should be OK # Check that IEC_Channel is not already in use.
- self.FindNewIEC_Channel(self.BaseParams.getIEC_Channel())
+ _self.FindNewIEC_Channel(_self.BaseParams.getIEC_Channel()) # Call the plugin real __init__
if getattr(PlugClass, "__init__", None):
PlugClass.__init__(_self)
@@ -319,31 +327,21 @@
- def LoadXMLParams(self, PlugName = None, test = True):
+ def LoadXMLParams(self, PlugName = None): - basexmlfilepath = self.PluginBaseXmlFilePath(PlugName)
- basexmlfile = open(basexmlfilepath, 'r')
+ if self.MandatoryParams: + basexmlfile = open(self.PluginBaseXmlFilePath(PlugName), 'r') basetree = minidom.parse(basexmlfile)
self.MandatoryParams[1].loadXMLTree(basetree.childNodes[0])
- xmlfilepath = self.PluginXmlFilePath(PlugName)
- xmlfile = open(xmlfilepath, 'r')
+ xmlfile = open(self.PluginXmlFilePath(PlugName), 'r') tree = minidom.parse(xmlfile)
self.PlugParams[1].loadXMLTree(tree.childNodes[0])
- # Basic check. Better to fail immediately.
- PlugName = os.path.split(self.PlugPath())[1].split(NameTypeSeparator)[0]
- if (self.BaseParams.getName() != PlugName):
- raise Exception, "Project tree layout do not match plugin.xml %s!=%s "%(PlugName, self.BaseParams.getName())
- # Now, self.PlugPath() should be OK
# Iterate over all PlugName@PlugType in plugin directory, and try to open them
for PlugDir in os.listdir(self.PlugPath()):
@@ -359,7 +357,38 @@
return getattr(__import__("plugins." + name), name).RootClass
+#################################################################################### +#################################################################################### +#################################################################################### +################################### ROOT ###################################### +#################################################################################### +#################################################################################### +#################################################################################### +iec2cc_path = os.path.join(base_folder, "matiec", "iec2cc") +ieclib_path = os.path.join(base_folder, "matiec", "lib") +# import for project creation timestamping +from time import localtime +from datetime import datetime +# import necessary stuff from PLCOpenEditor +from PLCControler import PLCControler +from PLCOpenEditor import PLCOpenEditor, ProjectDialog +from TextViewer import TextViewer +from plcopen.structures import IEC_KEYWORDS class PluginsRoot(PlugTemplate):
+ This class define Root object of the plugin tree. + - Managing project directory + - Handling PLCOpenEditor controler and view + - Loading user plugins and instanciante them as childs # For root object, available Childs Types are modules of the plugin packages.
PlugChildsTypes = [(name, _GetClassFunction(name)) for name in plugins.__all__]
@@ -418,26 +447,43 @@
- PlugTemplate.__init__(self)
+ def __init__(self, frame): + self.MandatoryParams = None + This method are not called here... but in NewProject and OpenProject + self._AddParamsMembers() + self.PluggedChilds = {} # Keep track of the plugin type name
self.PlugType = "Beremiz"
+ # After __init__ root plugin is not valid + self.ProjectPath = None def HasProjectOpened(self):
Return if a project is actually opened
- return self.ProjectPath != ""
+ return self.ProjectPath != None def GetProjectPath(self):
- def NewProject(self, ProjectPath, PLCParams):
+ def GetPlugInfos(self): + for child in self.IterChilds(): + childs.append(child.GetPlugInfos()) + return {"name" : os.path.split(self.ProjectPath)[1], "type" : None, "values" : childs} + def NewProject(self, ProjectPath): Create a new project in an empty folder
@param ProjectPath: path of the folder where project have to be created
@@ -446,6 +492,16 @@
# Verify that choosen folder is empty
if not os.path.isdir(ProjectPath) or len(os.listdir(ProjectPath)) > 0:
return "Folder choosen isn't empty. You can't use it for a new project!"
+ dialog = ProjectDialog(self.AppFrame) + if dialog.ShowModal() == wx.ID_OK: + values = dialog.GetValues() + values["creationDateTime"] = datetime(*localtime()[:6]) + return "Project not created" # Create Controler for PLCOpen program
self.PLCManager = PLCControler()
self.PLCManager.CreateNewProject(PLCParams.pop("projectName"))
@@ -453,11 +509,8 @@
# Change XSD into class members
- # No IEC channel, name, etc...
- self.MandatoryParams = []
# Keep track of the root plugin (i.e. project path)
self.ProjectPath = ProjectPath
- self.BaseParams.setName(os.path.split(ProjectPath)[1])
def LoadProject(self, ProjectPath):
@@ -478,19 +531,16 @@
# Change XSD into class members
- # No IEC channel, name, etc...
- self.MandatoryParams = None
# Keep track of the root plugin (i.e. project path)
self.ProjectPath = ProjectPath
# If dir have already be made, and file exist
if os.path.isdir(self.PlugPath()) and os.path.isfile(self.PluginXmlFilePath()):
#Load the plugin.xml file into parameters members
- result = self.LoadXMLParams(test = False)
+ result = self.LoadXMLParams() #Load and init all the childs
- self.BaseParams.setName(os.path.split(ProjectPath)[1])
@@ -501,9 +551,6 @@
def PlugPath(self, PlugName=None):
- def PluginBaseXmlFilePath(self, PlugName=None):
def PluginXmlFilePath(self, PlugName=None):
return os.path.join(self.PlugPath(PlugName), "beremiz.xml")
@@ -517,61 +564,93 @@
@return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND
return [(C_file_name, "") for C_file_name in self.PLCGeneratedCFiles ] , ""
- def _Generate_SoftPLC(self, buildpath, logger):
+ def _getBuildPath(self): + return os.path.join(self.ProjectPath, "build") + def _getIECcodepath(self): + # define name for IEC code file + return os.path.join(self._getBuildPath(), "plc.st") + def _Generate_SoftPLC(self, logger): + Generate SoftPLC ST/IL/SFC code out of PLCOpenEditor controller, and compile it with IEC2CC + @param buildpath: path where files should be created + @param logger: the log pseudo file + logger.write("Generating SoftPLC IEC-61131 ST/IL/SFC code...\n") + buildpath = self._getBuildPath() + # define name for IEC code file + plc_file = self._getIECcodepath() + # ask PLCOpenEditor controller to write ST/IL/SFC code file + result = self.PLCManager.GenerateProgram(plc_file) + logger.write_error("Error : ST/IL/SFC code generator returned %d"%result) + logger.write("Compiling ST Program in to C Program...\n") + # Now compile IEC code into many C files + # files are listed to stdout, and errors to stderr. + status, result, err_result = logger.LogCommand("%s %s -I %s %s"%(iec2cc_path, plc_file, ieclib_path, self.TargetDir)) + logger.write_error("Error : IEC to C compiler returned %d"%status) + # Now extract C files of stdout + C_files = result.splitlines() + # remove those that are not to be compiled because included by others + C_files.remove("POUS.c") + C_files.remove("LOCATED_VARIABLES.h") + # transform those base names to full names with path + C_files = map(lambda filename:os.path.join(self.TargetDir, filename), C_files) + logger.write("Extracting Located Variables...\n") + # IEC2CC compiler generate a list of located variables : LOCATED_VARIABLES.h + location_file = open(os.path.join(buildpath,"LOCATED_VARIABLES.h")) + # each line of LOCATED_VARIABLES.h declares a located variable + lines = [line.strip() for line in location_file.readlines()] + # This regular expression parses the lines genereated by IEC2CC LOCATED_MODEL = re.compile("__LOCATED_VAR\((?P<IEC_TYPE>[A-Z]*),(?P<NAME>[_A-Za-z0-9]*),(?P<DIR>[QMI])(?:,(?P<SIZE>[XBWD]))?,(?P<LOC>[,0-9]*)\)")
- logger.write("Generating SoftPLC IEC-61131 ST/IL/SFC code...\n")
- plc_file = os.path.join(self.TargetDir, "plc.st")
- result = self.PLCManager.GenerateProgram(plc_file)
- logger.write_error("Error : ST/IL/SFC code generator returned %d"%result)
- logger.write("Compiling ST Program in to C Program...\n")
- status, result, err_result = self.LogCommand("%s %s -I %s %s"%(iec2cc_path, plc_file, ieclib_path, self.TargetDir))
- new_dialog = wx.Frame(None)
- ST_viewer = TextViewer(new_dialog, None, None)
- #ST_viewer.Enable(False)
- ST_viewer.SetKeywords(IEC_KEYWORDS)
- ST_viewer.SetText(file(plc_file).read())
- raise Exception, "Error : IEC to C compiler returned %d"%status
- C_files = result.splitlines()
- C_files.remove("POUS.c")
- C_files = map(lambda filename:os.path.join(self.TargetDir, filename), C_files)
- logger.write("Extracting Located Variables...\n")
- location_file = open(os.path.join(self.TargetDir,"LOCATED_VARIABLES.h"))
- lines = [line.strip() for line in location_file.readlines()]
- result = LOCATED_MODEL.match(line)
- resdict = result.groupdict()
- # rewrite location as a tuple of integers
- resdict['LOC'] = tuple(map(int,resdict['LOC'].split(',')))
- if not resdict['SIZE']:
- locations.append(resdict)
+ result = LOCATED_MODEL.match(line) + # Get the resulting dict + resdict = result.groupdict() + # rewrite string for variadic location as a tuple of integers + resdict['LOC'] = tuple(map(int,resdict['LOC'].split(','))) + # set located size to 'X' if not given + if not resdict['SIZE']: + # finally store into located variable list + locations.append(resdict) + # Keep track of generated C files for later use by self.PlugGenerate_C self.PLCGeneratedCFiles = C_files
+ # Keep track of generated located variables for later use by self._Generate_C self.PLCGeneratedLocatedVars = locations
def _build(self, logger):
- buildpath = os.path.join(self.ProjectPath, "build")
+ Method called by user to (re)build SoftPLC and plugin tree + buildpath = self._getBuildPath() + # Eventually create build dir if not os.path.exists(buildpath):
logger.write("Start build in %s" % buildpath)
- if not self._Generate_SoftPLC(buildpath, logger):
- logger.write("SoftPLC code generation failed !")
+ # Generate SoftPLC code + if not self._Generate_SoftPLC(logger): + logger.write_error("SoftPLC code generation failed !") logger.write("SoftPLC code generation successfull")
+ # Generate C code and compilation params from plugin hierarchy CFilesAndCFLAGS, LDFLAGS = self._Generate_C(
@@ -581,14 +660,39 @@
logger.write_error("Plugins code generation Failed !")
logger.write_error(str(msg))
logger.write_error("Plugins code generation successfull")
+ # Compile the resulting code into object files. for CFile, CFLAG in CFilesAndCFLAGS:
+ # Link object files into something that can be executed on target - PluginMethods = [("Build",_build), ("Clean",None), ("Run",None), ("EditPLC",None), ("Simulate",None)]
+ def _showIECcode(self, logger): + plc_file = self._getIECcodepath() + new_dialog = wx.Frame(None) + ST_viewer = TextViewer(new_dialog, None, None) + #ST_viewer.Enable(False) + ST_viewer.SetKeywords(IEC_KEYWORDS) + text = file(plc_file).read() + text = '(* No IEC code have been generated at that time ! *)' + ST_viewer.SetText(text) + def _EditPLC(self, logger): + self.PLCEditor = PLCOpenEditor(self, self.PLCManager) + self.PLCEditor.RefreshProjectTree() + self.PLCEditor.RefreshFileMenu() + self.PLCEditor.RefreshEditMenu() + self.PLCEditor.RefreshToolBar() + PluginMethods = [("Build",_build), ("Clean",None), ("Run",None), ("EditPLC",None), ("Show IEC code",_showIECcode)]