# HG changeset patch # User etisserant # Date 1188230095 -7200 # Node ID f1f0edbeb3139d5a3496e850247785380f2e541d # Parent a1f9e514f708f285f925001e7fd330cec58a5e99 More precise design for plugins.... to be continued... diff -r a1f9e514f708 -r f1f0edbeb313 Beremiz.py --- a/Beremiz.py Tue Aug 21 17:21:26 2007 +0200 +++ b/Beremiz.py Mon Aug 27 17:54:55 2007 +0200 @@ -22,6 +22,8 @@ #License along with this library; if not, write to the Free Software #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +__version__ = "$Revision$" + import wx from time import localtime @@ -41,43 +43,6 @@ from plcopen.structures import IEC_KEYWORDS#, AddPlugin from PLCControler import PLCControler -import plugins - -__version__ = "$Revision$" - -def create(parent): - return Beremiz(parent) - -def usage(): - print "\nUsage of Beremiz.py :" - print "\n %s [Projectpath]\n"%sys.argv[0] - -try: - opts, args = getopt.getopt(sys.argv[1:], "h", ["help"]) -except getopt.GetoptError: - # print help information and exit: - usage() - sys.exit(2) - -for o, a in opts: - if o in ("-h", "--help"): - usage() - sys.exit() - -projectOpen = None -if len(args) > 1: - usage() - sys.exit() -elif len(args) == 1: - projectOpen = args[0] -CWD = sys.path[0] - -re_texts = {} -re_texts["letter"] = "[A-Za-z]" -re_texts["digit"] = "[0-9]" -LOCATED_MODEL = re.compile("__LOCATED_VAR\(([A-Z]*),([_A-Za-z0-9]*)\)") - - class LogPseudoFile: """ Base class for file like objects to facilitate StdOut for the Shell.""" def __init__(self, output = None): @@ -328,8 +293,8 @@ id=ID_BEREMIZDELETEBUSBUTTON) self._init_sizers() - - def __init__(self, parent): + + def __init__(self, parent, projectOpen): self._init_ctrls(parent) for name in plugins.__all__: @@ -680,6 +645,8 @@ return (err, outdata, errdata) def BuildAutom(self): + LOCATED_MODEL = re.compile("__LOCATED_VAR\(([A-Z]*),([_A-Za-z0-9]*)\)") + if self.PLCManager: self.TargetDir = os.path.join(self.CurrentProjectPath, "build") if not os.path.exists(self.TargetDir): @@ -710,7 +677,7 @@ locations = [] lines = [line.strip() for line in location_file.readlines()] for line in lines: - result = LOCATED_MODEL.match(line) + result = self.LOCATED_MODEL.match(line) if result: locations.append(result.groups()) self.Log.write("Generating Network Configurations...\n") @@ -979,13 +946,37 @@ sys.excepthook = handle_exception if __name__ == '__main__': + def usage(): + print "\nUsage of Beremiz.py :" + print "\n %s [Projectpath]\n"%sys.argv[0] + + try: + opts, args = getopt.getopt(sys.argv[1:], "h", ["help"]) + except getopt.GetoptError: + # print help information and exit: + usage() + sys.exit(2) + + for o, a in opts: + if o in ("-h", "--help"): + usage() + sys.exit() + + if len(args) > 1: + usage() + sys.exit() + elif len(args) == 1: + projectOpen = args[0] + else: + projectOpen = None + app = wx.PySimpleApp() wx.InitAllImageHandlers() # Install a exception handle for bug reports AddExceptHook(os.getcwd(),__version__) - frame = Beremiz(None) + frame = Beremiz(None, projectOpen) frame.Show() app.MainLoop() diff -r a1f9e514f708 -r f1f0edbeb313 plugger.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugger.py Mon Aug 27 17:54:55 2007 +0200 @@ -0,0 +1,90 @@ +import os +import plugins +from plugins import PlugTemplate + + +class PluginsRoot(PlugTemplate): + + # A special PlugChildsTypes + PlugChildsTypes = [(name,lambda : getattr(__import__("plugins." + name), name).RootClass) for name in plugins.__all__] + + XSD = """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + """ + + def __init__(self, ProjectPath): + # self is the parent + self.PlugParent = None + # Keep track of the plugin type name + self.PlugType = "Beremiz" + # Keep track of the root plugin (i.e. project path) + self.ProjectPath = ProjectPath + # Change XSD into class members + self._AddParamsMembers() + self.PluggedChilds = {} + # No IEC channel, name, etc... + self.MandatoryParams = [] + # If dir have already be made, and file exist + if os.path.isdir(_self.PlugPath(PlugName)) and os.path.isfile(_self.PluginXmlFilePath(PlugName)): + #Load the plugin.xml file into parameters members + _self.LoadXMLParams() + #Load and init all the childs + _self.LoadChilds() + + def PlugPath(self,PlugName=None): + return self.ProjectPath + + def PluginXmlFilePath(self, PlugName=None): + return os.path.join(self.PlugPath(PlugName), "beremiz.xml") + + diff -r a1f9e514f708 -r f1f0edbeb313 plugins/__init__.py --- a/plugins/__init__.py Tue Aug 21 17:21:26 2007 +0200 +++ b/plugins/__init__.py Mon Aug 27 17:54:55 2007 +0200 @@ -1,11 +1,10 @@ from os import listdir, path -from xmlclass import DeclareXSDClass -from __templates import * +from __templates import PlugTemplate _base_path = path.split(__file__)[0] __all__ = [name for name in listdir(_base_path) if path.isdir(path.join(_base_path, name)) and name != "CVS" or name.endswith(".py") and not name.startswith("__")] -for name in __all__: - __import__(name, globals(), locals(), []) +#for name in __all__: +# __import__(name, globals(), locals(), []) diff -r a1f9e514f708 -r f1f0edbeb313 plugins/__templates.py --- a/plugins/__templates.py Tue Aug 21 17:21:26 2007 +0200 +++ b/plugins/__templates.py Mon Aug 27 17:54:55 2007 +0200 @@ -1,72 +1,289 @@ -" Here are base type definitions for plugins " - -class PluggableTemplate: - - XSD = None - - def __init__(self, buspath): - pass - - def TestModified(self): - return False - - def ReqSave(self): - return False - - def Generate_C(self, dirpath, locations): - return [] # [filenames, ...] - - def BlockTypesFactory(self): - return [] - - def STLibraryFactory(self): - return "" - - ViewClass = None - View = None - def ViewFactory(self): - if self.ViewClass: - if not self.View: - def _onclose(): - self.View = None - self.View = self.ViewClass() - self.View.OnPluggClose = _onclose - return self.View - return None - - -def _do_BaseParamsClasses(): - Classes = {} - Types = {} - GenerateClassesFromXSDstring(""" +""" +Base definitions for beremiz plugins +""" + +import os +import types +import shutil +from xml.dom import minidom + +_BaseParamsClass = GenerateClassesFromXSDstring(""" - + + + - - """) - CreateClasses(Classes, Types) - - PluginsBaseParamsClass = Classes["BaseParams"] - - Classes = {} - Types = {} - GenerateClassesFromXSDstring(""" - - - - - - - - - """) - CreateClasses(Classes, Types) - - BusBaseParamsClass = Classes["BaseParams"] - return PluginsBaseParamsClass, BusBaseParamsClass - -PluginsBaseParamsClass, BusBaseParamsClass = _do_BaseParamsClasses() - + """)[0]["BaseParams"] + +NameTypeSeparator = '@' + +class PlugTemplate: + """ + This class is the one that define plugins. + """ + + XSD = None + PlugChildsTypes = [] + PlugMaxCount = None + PluginMethods = [] + + def _AddParamsMembers(self): + Classes = GenerateClassesFromXSDstring(self.XSD)[0] + self.PlugParams = [] + for name, XSDclass in Classes.items(): + if XSDclass.IsBaseClass: + obj = XSDclass() + self.PlugParams.append( (name, obj) ) + setattr(self, name, obj) + + def __init__(self, PlugPath): + # Create BaseParam + self.BaseParams = _BaseParamsClass() + self.MandatoryParams = [("BaseParams", self.BaseParams)] + self._AddParamsMembers() + self.PluggedChilds = {} + + def PluginXmlFilePath(self, PlugName=None): + return os.path.join(self.PlugPath(PlugName), "plugin.xml") + + def PlugPath(self,PlugName=None): + if not PlugName: + PlugName = self.BaseParams.getName() + return os.path.join(self.PlugParent.PlugPath(), PlugName + NameTypeSeparator + self.PlugType) + + def PlugTestModified(self): + return False + + def OnPlugSave(self): + return True + + def PlugRequestSave(self): + # If plugin do not have corresponding directory + if not os.path.isdir(self.PlugPath(PlugName)): + # Create it + os.mkdir(self.PlugPath(PlugName)) + + # generate XML for all XML parameters controllers of the plugin + XMLString = '' + for nodeName, XMLController in self.PlugParams + self.MandatoryParams: + XMLString += XMLController.generateXMLTextMethod(self, nodeName, 0) + XMLFile = open(self.PluginXmlFilePath(PlugName),'w') + XMLFile.write(XMLString) + XMLFile.close() + + # Call the plugin specific OnPlugSave method + self.OnPlugSave() + + # go through all childs and do the same + for PlugChild in self.IterChilds(): + PlugChild.PlugRequestSave() + + def PlugImport(self, src_PlugPath): + shutil.copytree(src_PlugPath, self.PlugPath) + return True + + def PlugGenerate_C(self, buildpath, current_location, 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_loc, IEC_Direction IEC_Type, Name)]\ + ex: [((0,0,4,5),'I','X','__IX_0_0_4_5'),...] + """ + return [],"" + + def _Generate_C(self, buildpath, current_location, locations): + # Generate plugins [(Cfiles, CFLAGS)], LDFLAGS + PlugCFilesAndCFLAGS, PlugLDFLAGS = self._Generate_C(buildpath, current_location, locations) + # recurse through all childs, and stack their results + for PlugChild in self.IterChilds(): + # Get childs [(Cfiles, CFLAGS)], LDFLAGS + CFilesAndCFLAGS, LDFLAGS = \ + PlugChild._Generate_C( + #keep the same path + buildpath, + # but update location (add curent IEC channel at the end) + current_location + (self.BaseParams.getIEC_Channel()), + # filter locations that start with current IEC location + [ (l,d,t,n) for l,d,t,n in locations if l[0:len(current_location)] == current_location ]) + # stack the result + PlugCFilesAndCFLAGS += CFilesAndCFLAGS + PlugLDFLAGS += LDFLAGS + + return PlugCFilesAndCFLAGS,PlugLDFLAGS + + def BlockTypesFactory(self): + return [] + + def STLibraryFactory(self): + return "" + + def IterChilds(self): + for PlugType, PluggedChilds in self.PluggedChilds.items(): + for PlugInstance in PluggedChilds: + yield PlugInstance + + def _GetChildBySomething(self, sep, something, matching): + toks = matching.split(sep,1) + for PlugInstance in self.IterChilds: + # if match component of the name + if getattr(PlugInstance.BaseParams, something) == toks[0]: + # if Name have other components + if len(toks) == 2: + # Recurse in order to find the latest object + return PlugInstance._GetChildBySomething( sep, something, toks[1]) + # No sub name -> found + return PlugInstance + # Not found + return None + + def GetChildByName(self, Name): + return self._GetChildBySomething('.',"Name", Name) + + def GetChildByIECLocation(self, Location): + return self._GetChildBySomething('_',"IEC_Channel", Name) + + def FindNewIEC_Channel(self, DesiredChannel): + """ + Changes IEC Channel number to DesiredChannel if available, nearest available if not. + @param DesiredChannel: The desired IEC channel (int) + """ + # Get Current IEC channel + CurrentChannel = self.BaseParams.getIEC_Channel() + # Do nothing if no change + if CurrentChannel == DesiredChannel: return CurrentChannel + # Build a list of used Channels out of parent's PluggedChilds + AllChannels=[] + for PlugInstance in self.PlugParent.IterChilds(): + if PlugInstance != self: + AllChannels.append(PlugInstance.BaseParams.getIEC_Channel()) + AllChannels.sort() + + # Now, try to guess the nearest available channel + res = DesiredChannel + while res in AllChannels: # While channel not free + if res < CurrentChannel: # Want to go down ? + res -= 1 # Test for n-1 + if res < 0 : return CurrentChannel # Can't go bellow 0, do nothing + else : # Want to go up ? + res += 1 # Test for n-1 + # Finally set IEC Channel + self.BaseParams.setIEC_Channel(res) + return res + + def OnPlugClose(self): + return True + + def _doRemoveChild(self, PlugInstance): + # Remove all childs of child + for SubPlugInstance in PlugInstance.IterChilds(): + PlugInstance._doRemoveChild(SubPlugInstance) + # Call the OnCloseMethod + PlugInstance.OnPlugClose() + # Delete plugin dir + shutil.rmtree(PlugInstance.PlugPath()) + # Remove child of PluggedChilds + self.PluggedChilds[PlugInstance.PlugType].remove(PlugInstance) + # Forget it... (View have to refresh) + + def PlugRemoveChild(self, PlugName): + # Fetch the plugin + PlugInstance = self.GetChildByName(PlugName) + # Ask to his parent to remove it + PlugInstance.PlugParent._doRemoveChild(PlugInstance) + + def PlugAddChild(self, PlugName, PlugType): + """ + Create the plugins that may be added as child to this node self + @param PlugType: string desining the plugin class name (get name from PlugChildsTypes) + @param PlugName: string for the name of the plugin instance + """ + PlugChildsTypes = dict(self.PlugChildsTypes) + # Check that adding this plugin is allowed + try: + PlugClass = PlugChildsTypes[PlugType] + except KeyError: + raise Exception, "Cannot create child %s of type %s "%(PlugName, PlugType) + + # if PlugClass is a class factory, call it. (prevent unneeded imports) + if type(PlugClass) == types.FunctionType: + PlugClass = PlugClass() + + # Eventualy Initialize child instance list for this class of plugin + PluggedChildsWithSameClass = self.PluggedChilds.setdefault(PlugType,list()) + # Check count + if PlugClass.MaxCount and len(PluggedChildsWithSameClass) >= PlugClass.MaxCount: + raise Exception, "Max count (%d) reached for this plugin of type %s "%(PlugClass.MaxCount, PlugType) + + # create the final class, derived of provided plugin and template + class FinalPlugClass(PlugClass, PlugTemplate): + """ + Plugin class is derivated into FinalPlugClass before being instanciated + This way __init__ is overloaded to ensure PlugTemplate.__init__ is called + before PlugClass.__init__, and to do the file related stuff. + """ + def __init__(_self): + # self is the parent + _self.PlugParent = self + # Keep track of the plugin type name + _self.PlugType = PlugType + # Call the base plugin template init - change XSD into class members + PlugTemplate.__init__(_self) + # If dir have already be made, and file exist + if os.path.isdir(_self.PlugPath(PlugName)) and os.path.isfile(_self.PluginXmlFilePath(PlugName)): + #Load the plugin.xml file into parameters members + _self.LoadXMLParams() + # Call the plugin real __init__ + PlugClass.__init__(_self) + #Load and init all the childs + _self.LoadChilds() + # Check that IEC_Channel is not already in use. + self.FindNewIEC_Channel(self.BaseParams.getIEC_Channel()) + else: + # If plugin do not have corresponding file/dirs - they will be created on Save + # Set plugin name + _self.BaseParams.setName(PlugName) + # Find an IEC number + _self.FindNewIEC_Channel(0) + # Call the plugin real __init__ + PlugClass.__init__(_self) + + # Create the object out of the resulting class + newPluginOpj = FinalPlugClass() + # Store it in PluggedChils + PluggedChildsWithSameClass.append(newPluginOpj) + + return newPluginOpj + + + def LoadXMLParams(self): + # PlugParams have been filled, make a local dict to work with + PlugParams = dict(self.PlugParams + self.MandatoryParams) + # Get the xml tree + xmlfile = open(self.PluginXmlFilePath(PlugName), 'r') + tree = minidom.parse(xmlfile) + xmlfile.close() + # for each root elements + for subtree in tree.childNodes: + # if a plugin specific parameter set + if subtree.nodeName in PlugParams: + #Load into associated xmlclass. + PlugParams[subtree.nodeName].loadXMLTree(subtree) + + # 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 + + def LoadChilds(self): + # Iterate over all PlugName@PlugType in plugin directory, and try to open them + for PlugDir in os.listdir(self.PlugPath()): + if os.path.isdir(os.path.join(self.PlugPath(),PlugDir)) and \ + PlugDir.count(NameTypeSeparator) == 1: + try: + self.PlugAddChild(*PlugDir.split[NameTypeSeparator]) + except Exception, e: + print e diff -r a1f9e514f708 -r f1f0edbeb313 plugins/canfestival/canfestival.py --- a/plugins/canfestival/canfestival.py Tue Aug 21 17:21:26 2007 +0200 +++ b/plugins/canfestival/canfestival.py Mon Aug 27 17:54:55 2007 +0200 @@ -4,12 +4,14 @@ import config_utils, gen_cfile from networkedit import networkedit -class _NetworkEditPlugg(networkedit): +class _NetworkEdit(networkedit): + " Overload some of CanFestival Network Editor methods " def OnCloseFrame(self, event): - self.OnPluggClose() + " Do reset _NodeListPlug.View when closed" + self._onclose() event.Skip() -class BusController(NodeList): +class _NodeListPlug(NodeList): XSD = """ @@ -20,17 +22,29 @@ """ - ViewClass = _NetworkEditPlugg - def __init__(self, buspath): manager = NodeManager() NodeList.__init__(self, manager) self.LoadProject(buspath) - def TestModified(self): + _View = None + def _OpenView(self): + if not self._View: + def _onclose(): + self.View = None + self._View = _NetworkEdit() + self._View._onclose = _onclose + return self.View + PluginMethods = [("NetworkEdit",_OpenView)] + + def OnPlugClose(self): + if self._View: + self._View.Close() + + def PlugTestModified(self): return self.HasChanged() - def ReqSave(self): + def PlugRequestSave(self): self.SaveProject() return True @@ -43,14 +57,14 @@ res = gen_cfile.GenerateFile(filepath, master) if not res: s = str(self.BaseParams.BusId)+"_IN(){}\n" - s += "CanOpen(str(\""+self.CanFestivalNode.CAN_Device)+"\")" + s += "CanOpen(\""+self.CanFestivalNode.CAN_Device+"\")" f = file(filepath, 'a') f.write(s) else: pass # error return {"headers":["master.h"],"sources":["master.c"]} -class PluginController: +class RootClass: XSD = """ @@ -61,6 +75,8 @@ """ + PlugChildsTypes = [("CanOpenNode",_NodeListPlug)] + def Generate_C(self, filepath, locations): """ return C code for network dictionnary @@ -69,7 +85,7 @@ res = gen_cfile.GenerateFile(filepath, master) if not res: s = str(self.BaseParams.BusId)+"_IN(){}\n" - s += "CanOpen(str(\""+self.CanFestivalNode.CAN_Device)+"\")" + s += "CanOpen(str(\""+self.CanFestivalNode.CAN_Device+"\")" f = file(filepath, 'a') f.write(s) else: diff -r a1f9e514f708 -r f1f0edbeb313 plugins/svgui/svgui.py --- a/plugins/svgui/svgui.py Tue Aug 21 17:21:26 2007 +0200 +++ b/plugins/svgui/svgui.py Mon Aug 27 17:54:55 2007 +0200 @@ -2,20 +2,20 @@ from DEFControler import DEFControler from defeditor import EditorFrame -class _EditorFramePlugg(EditorFrame): +class _EditorFramePlug(EditorFrame): def OnClose(self, event): - self.OnPluggClose() + self.OnPlugClose() event.Skip() -class BusController(DEFControler): +class _DEFControlerPlug(DEFControler): - ViewClass = _EditorFramePlugg + ViewClass = _EditorFramePlug def __init__(self, buspath): filepath = os.path.join(buspath, "gui.def") if os.path.isfile(filepath): self.OpenXMLFile(filepath) - else + else: self.CreateRootElement() self.SetFilePath(filepath) @@ -31,7 +31,10 @@ "USINT" : "B", "UINT" : "W", "UDINT" : "D", "ULINT" : "L", "REAL" : "D", "LREAL" : "L", "STRING" : "B", "BYTE" : "B", "WORD" : "W", "DWORD" : "D", "LWORD" : "L", "WSTRING" : "W"} -class PluginController: +class RootClass: + + ChildsType = _DEFControlerPlug + def BlockTypesFactory(self): def generate_svgui_block(generator, block, body, link): controller = generator.GetController() @@ -39,7 +42,7 @@ type = block.getTypeName() block_infos = GetBlockType(type) bus_id, name = [word for word in name.split("_") if word != ""] - block_id = self.PluginBuses[bus_id].GetElementIdFromName(name) + block_id = self.PlugChilds[bus_id].GetElementIdFromName(name) if block_id == None: raise ValueError, "No corresponding block found" if not generator.ComputedBlocks.get(name, False):