ConfigTree.py
changeset 717 1c23952dbde1
parent 716 180e4a7d945c
child 718 5d4dc150b956
equal deleted inserted replaced
716:180e4a7d945c 717:1c23952dbde1
       
     1 """
       
     2 Base definitions for beremiz confnodes
       
     3 """
       
     4 
       
     5 import os,sys,traceback
       
     6 import time
       
     7 import confnodes
       
     8 import types
       
     9 import shutil
       
    10 from xml.dom import minidom
       
    11 import wx
       
    12 
       
    13 #Quick hack to be able to find Beremiz IEC tools. Should be config params.
       
    14 base_folder = os.path.split(sys.path[0])[0]
       
    15 
       
    16 from xmlclass import GenerateClassesFromXSDstring
       
    17 from wxPopen import ProcessLogger
       
    18 
       
    19 from PLCControler import PLCControler, LOCATION_CONFNODE, LOCATION_MODULE, LOCATION_GROUP, LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, LOCATION_VAR_MEMORY
       
    20 
       
    21 _BaseParamsClass = GenerateClassesFromXSDstring("""<?xml version="1.0" encoding="ISO-8859-1" ?>
       
    22         <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
       
    23           <xsd:element name="BaseParams">
       
    24             <xsd:complexType>
       
    25               <xsd:attribute name="Name" type="xsd:string" use="optional" default="__unnamed__"/>
       
    26               <xsd:attribute name="IEC_Channel" type="xsd:integer" use="required"/>
       
    27               <xsd:attribute name="Enabled" type="xsd:boolean" use="optional" default="true"/>
       
    28             </xsd:complexType>
       
    29           </xsd:element>
       
    30         </xsd:schema>""")["BaseParams"]
       
    31 
       
    32 NameTypeSeparator = '@'
       
    33 
       
    34 class MiniTextControler:
       
    35     
       
    36     def __init__(self, filepath):
       
    37         self.FilePath = filepath
       
    38     
       
    39     def PlugFullName(self):
       
    40         return ""
       
    41     
       
    42     def SetEditedElementText(self, tagname, text):
       
    43         file = open(self.FilePath, "w")
       
    44         file.write(text)
       
    45         file.close()
       
    46         
       
    47     def GetEditedElementText(self, tagname, debug = False):
       
    48         if os.path.isfile(self.FilePath):
       
    49             file = open(self.FilePath, "r")
       
    50             text = file.read()
       
    51             file.close()
       
    52             return text
       
    53         return ""
       
    54     
       
    55     def GetEditedElementInterfaceVars(self, tagname, debug = False):
       
    56         return []
       
    57     
       
    58     def GetEditedElementType(self, tagname, debug = False):
       
    59         return "program"
       
    60     
       
    61     def GetBlockTypes(self, tagname = "", debug = False):
       
    62         return []
       
    63     
       
    64     def GetDataTypes(self, tagname = "", basetypes = True, only_locatables = False, debug = False):
       
    65         return []
       
    66     
       
    67     def GetEnumeratedDataValues(self, debug = False):
       
    68         return []
       
    69     
       
    70     def StartBuffering(self):
       
    71         pass
       
    72 
       
    73     def EndBuffering(self):
       
    74         pass
       
    75 
       
    76     def BufferProject(self):
       
    77         pass
       
    78 
       
    79 # helper func to get path to images
       
    80 def opjimg(imgname):
       
    81     return os.path.join(base_folder, "beremiz", "images",imgname)
       
    82     
       
    83 # helper func to check path write permission
       
    84 def CheckPathPerm(path):
       
    85     if path is None or not os.path.isdir(path):
       
    86         return False
       
    87     for root, dirs, files in os.walk(path):
       
    88          for name in files:
       
    89              if os.access(root, os.W_OK) is not True or os.access(os.path.join(root, name), os.W_OK) is not True:
       
    90                  return False
       
    91     return True
       
    92     
       
    93 class ConfigTreeNode:
       
    94     """
       
    95     This class is the one that define confnodes.
       
    96     """
       
    97 
       
    98     XSD = None
       
    99     PlugChildsTypes = []
       
   100     PlugMaxCount = None
       
   101     ConfNodeMethods = []
       
   102     LibraryControler = None
       
   103     EditorType = None
       
   104 
       
   105     def _AddParamsMembers(self):
       
   106         self.PlugParams = None
       
   107         if self.XSD:
       
   108             self.Classes = GenerateClassesFromXSDstring(self.XSD)
       
   109             Classes = [(name, XSDclass) for name, XSDclass in self.Classes.items() if XSDclass.IsBaseClass]
       
   110             if len(Classes) == 1:
       
   111                 name, XSDclass = Classes[0]
       
   112                 obj = XSDclass()
       
   113                 self.PlugParams = (name, obj)
       
   114                 setattr(self, name, obj)
       
   115 
       
   116     def __init__(self):
       
   117         # Create BaseParam 
       
   118         self.BaseParams = _BaseParamsClass()
       
   119         self.MandatoryParams = ("BaseParams", self.BaseParams)
       
   120         self._AddParamsMembers()
       
   121         self.PluggedChilds = {}
       
   122         self._View = None
       
   123         # copy ConfNodeMethods so that it can be later customized
       
   124         self.ConfNodeMethods = [dic.copy() for dic in self.ConfNodeMethods]
       
   125         self.LoadSTLibrary()
       
   126         
       
   127     def ConfNodeBaseXmlFilePath(self, PlugName=None):
       
   128         return os.path.join(self.PlugPath(PlugName), "baseconfnode.xml")
       
   129     
       
   130     def ConfNodeXmlFilePath(self, PlugName=None):
       
   131         return os.path.join(self.PlugPath(PlugName), "confnode.xml")
       
   132 
       
   133     def ConfNodeLibraryFilePath(self):
       
   134         return os.path.join(self.ConfNodePath(), "pous.xml")
       
   135 
       
   136     def ConfNodePath(self):
       
   137         return os.path.join(self.PlugParent.ConfNodePath(), self.PlugType)
       
   138 
       
   139     def PlugPath(self,PlugName=None):
       
   140         if not PlugName:
       
   141             PlugName = self.PlugName()
       
   142         return os.path.join(self.PlugParent.PlugPath(),
       
   143                             PlugName + NameTypeSeparator + self.PlugType)
       
   144     
       
   145     def PlugName(self):
       
   146         return self.BaseParams.getName()
       
   147     
       
   148     def PlugEnabled(self):
       
   149         return self.BaseParams.getEnabled()
       
   150     
       
   151     def PlugFullName(self):
       
   152         parent = self.PlugParent.PlugFullName()
       
   153         if parent != "":
       
   154             return parent + "." + self.PlugName()
       
   155         return self.BaseParams.getName()
       
   156     
       
   157     def GetIconPath(self, name):
       
   158         return opjimg(name)
       
   159     
       
   160     def PlugTestModified(self):
       
   161         return self.ChangesToSave
       
   162 
       
   163     def ProjectTestModified(self):
       
   164         """
       
   165         recursively check modified status
       
   166         """
       
   167         if self.PlugTestModified():
       
   168             return True
       
   169 
       
   170         for PlugChild in self.IterChilds():
       
   171             if PlugChild.ProjectTestModified():
       
   172                 return True
       
   173 
       
   174         return False
       
   175     
       
   176     def RemoteExec(self, script, **kwargs):
       
   177         return self.PlugParent.RemoteExec(script, **kwargs)
       
   178     
       
   179     def OnPlugSave(self):
       
   180         #Default, do nothing and return success
       
   181         return True
       
   182 
       
   183     def GetParamsAttributes(self, path = None):
       
   184         if path:
       
   185             parts = path.split(".", 1)
       
   186             if self.MandatoryParams and parts[0] == self.MandatoryParams[0]:
       
   187                 return self.MandatoryParams[1].getElementInfos(parts[0], parts[1])
       
   188             elif self.PlugParams and parts[0] == self.PlugParams[0]:
       
   189                 return self.PlugParams[1].getElementInfos(parts[0], parts[1])
       
   190         else:
       
   191             params = []
       
   192             if wx.VERSION < (2, 8, 0) and self.MandatoryParams:
       
   193                 params.append(self.MandatoryParams[1].getElementInfos(self.MandatoryParams[0]))
       
   194             if self.PlugParams:
       
   195                 params.append(self.PlugParams[1].getElementInfos(self.PlugParams[0]))
       
   196             return params
       
   197         
       
   198     def SetParamsAttribute(self, path, value):
       
   199         self.ChangesToSave = True
       
   200         # Filter IEC_Channel and Name, that have specific behavior
       
   201         if path == "BaseParams.IEC_Channel":
       
   202             old_leading = ".".join(map(str, self.GetCurrentLocation()))
       
   203             new_value = self.FindNewIEC_Channel(value)
       
   204             new_leading = ".".join(map(str, self.PlugParent.GetCurrentLocation() + (new_value,)))
       
   205             self.GetPlugRoot().UpdateProjectVariableLocation(old_leading, new_leading)
       
   206             return new_value, True
       
   207         elif path == "BaseParams.Name":
       
   208             res = self.FindNewName(value)
       
   209             self.PlugRequestSave()
       
   210             return res, True
       
   211         
       
   212         parts = path.split(".", 1)
       
   213         if self.MandatoryParams and parts[0] == self.MandatoryParams[0]:
       
   214             self.MandatoryParams[1].setElementValue(parts[1], value)
       
   215         elif self.PlugParams and parts[0] == self.PlugParams[0]:
       
   216             self.PlugParams[1].setElementValue(parts[1], value)
       
   217         return value, False
       
   218 
       
   219     def PlugMakeDir(self):
       
   220         os.mkdir(self.PlugPath())
       
   221 
       
   222     def PlugRequestSave(self):
       
   223         if self.GetPlugRoot().CheckProjectPathPerm(False):
       
   224             # If confnode do not have corresponding directory
       
   225             plugpath = self.PlugPath()
       
   226             if not os.path.isdir(plugpath):
       
   227                 # Create it
       
   228                 os.mkdir(plugpath)
       
   229     
       
   230             # generate XML for base XML parameters controller of the confnode
       
   231             if self.MandatoryParams:
       
   232                 BaseXMLFile = open(self.ConfNodeBaseXmlFilePath(),'w')
       
   233                 BaseXMLFile.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
       
   234                 BaseXMLFile.write(self.MandatoryParams[1].generateXMLText(self.MandatoryParams[0], 0).encode("utf-8"))
       
   235                 BaseXMLFile.close()
       
   236             
       
   237             # generate XML for XML parameters controller of the confnode
       
   238             if self.PlugParams:
       
   239                 XMLFile = open(self.ConfNodeXmlFilePath(),'w')
       
   240                 XMLFile.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
       
   241                 XMLFile.write(self.PlugParams[1].generateXMLText(self.PlugParams[0], 0).encode("utf-8"))
       
   242                 XMLFile.close()
       
   243             
       
   244             # Call the confnode specific OnPlugSave method
       
   245             result = self.OnPlugSave()
       
   246             if not result:
       
   247                 return _("Error while saving \"%s\"\n")%self.PlugPath()
       
   248     
       
   249             # mark confnode as saved
       
   250             self.ChangesToSave = False
       
   251             # go through all childs and do the same
       
   252             for PlugChild in self.IterChilds():
       
   253                 result = PlugChild.PlugRequestSave()
       
   254                 if result:
       
   255                     return result
       
   256         return None
       
   257     
       
   258     def PlugImport(self, src_PlugPath):
       
   259         shutil.copytree(src_PlugPath, self.PlugPath)
       
   260         return True
       
   261 
       
   262     def PlugGenerate_C(self, buildpath, locations):
       
   263         """
       
   264         Generate C code
       
   265         @param locations: List of complete variables locations \
       
   266             [{"IEC_TYPE" : the IEC type (i.e. "INT", "STRING", ...)
       
   267             "NAME" : name of the variable (generally "__IW0_1_2" style)
       
   268             "DIR" : direction "Q","I" or "M"
       
   269             "SIZE" : size "X", "B", "W", "D", "L"
       
   270             "LOC" : tuple of interger for IEC location (0,1,2,...)
       
   271             }, ...]
       
   272         @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND
       
   273         """
       
   274         self.GetPlugRoot().logger.write_warning(".".join(map(lambda x:str(x), self.GetCurrentLocation())) + " -> Nothing to do\n")
       
   275         return [],"",False
       
   276     
       
   277     def _Generate_C(self, buildpath, locations):
       
   278         # Generate confnodes [(Cfiles, CFLAGS)], LDFLAGS, DoCalls, extra_files
       
   279         # extra_files = [(fname,fobject), ...]
       
   280         gen_result = self.PlugGenerate_C(buildpath, locations)
       
   281         PlugCFilesAndCFLAGS, PlugLDFLAGS, DoCalls = gen_result[:3]
       
   282         extra_files = gen_result[3:]
       
   283         # if some files have been generated put them in the list with their location
       
   284         if PlugCFilesAndCFLAGS:
       
   285             LocationCFilesAndCFLAGS = [(self.GetCurrentLocation(), PlugCFilesAndCFLAGS, DoCalls)]
       
   286         else:
       
   287             LocationCFilesAndCFLAGS = []
       
   288 
       
   289         # confnode asks for some LDFLAGS
       
   290         if PlugLDFLAGS:
       
   291             # LDFLAGS can be either string
       
   292             if type(PlugLDFLAGS)==type(str()):
       
   293                 LDFLAGS=[PlugLDFLAGS]
       
   294             #or list of strings
       
   295             elif type(PlugLDFLAGS)==type(list()):
       
   296                 LDFLAGS=PlugLDFLAGS[:]
       
   297         else:
       
   298             LDFLAGS=[]
       
   299         
       
   300         # recurse through all childs, and stack their results
       
   301         for PlugChild in self.IECSortedChilds():
       
   302             new_location = PlugChild.GetCurrentLocation()
       
   303             # How deep are we in the tree ?
       
   304             depth=len(new_location)
       
   305             _LocationCFilesAndCFLAGS, _LDFLAGS, _extra_files = \
       
   306                 PlugChild._Generate_C(
       
   307                     #keep the same path
       
   308                     buildpath,
       
   309                     # filter locations that start with current IEC location
       
   310                     [loc for loc in locations if loc["LOC"][0:depth] == new_location ])
       
   311             # stack the result
       
   312             LocationCFilesAndCFLAGS += _LocationCFilesAndCFLAGS
       
   313             LDFLAGS += _LDFLAGS
       
   314             extra_files += _extra_files
       
   315         
       
   316         return LocationCFilesAndCFLAGS, LDFLAGS, extra_files
       
   317 
       
   318     def ConfNodeTypesFactory(self):
       
   319         if self.LibraryControler is not None:
       
   320             return [{"name" : self.PlugType, "types": self.LibraryControler.Project}]
       
   321         return []
       
   322 
       
   323     def ParentsTypesFactory(self):
       
   324         return self.PlugParent.ParentsTypesFactory() + self.ConfNodeTypesFactory()
       
   325 
       
   326     def ConfNodesTypesFactory(self):
       
   327         list = self.ConfNodeTypesFactory()
       
   328         for PlugChild in self.IterChilds():
       
   329             list += PlugChild.ConfNodesTypesFactory()
       
   330         return list
       
   331 
       
   332     def STLibraryFactory(self):
       
   333         if self.LibraryControler is not None:
       
   334             program, errors, warnings = self.LibraryControler.GenerateProgram()
       
   335             return program + "\n"
       
   336         return ""
       
   337 
       
   338     def ConfNodesSTLibraryFactory(self):
       
   339         program = self.STLibraryFactory()
       
   340         for PlugChild in self.IECSortedChilds():
       
   341             program += PlugChild.ConfNodesSTLibraryFactory()
       
   342         return program
       
   343         
       
   344     def IterChilds(self):
       
   345         for PlugType, PluggedChilds in self.PluggedChilds.items():
       
   346             for PlugInstance in PluggedChilds:
       
   347                 yield PlugInstance
       
   348     
       
   349     def IECSortedChilds(self):
       
   350         # reorder childs by IEC_channels
       
   351         ordered = [(chld.BaseParams.getIEC_Channel(),chld) for chld in self.IterChilds()]
       
   352         if ordered:
       
   353             ordered.sort()
       
   354             return zip(*ordered)[1]
       
   355         else:
       
   356             return []
       
   357     
       
   358     def _GetChildBySomething(self, something, toks):
       
   359         for PlugInstance in self.IterChilds():
       
   360             # if match component of the name
       
   361             if getattr(PlugInstance.BaseParams, something) == toks[0]:
       
   362                 # if Name have other components
       
   363                 if len(toks) >= 2:
       
   364                     # Recurse in order to find the latest object
       
   365                     return PlugInstance._GetChildBySomething( something, toks[1:])
       
   366                 # No sub name -> found
       
   367                 return PlugInstance
       
   368         # Not found
       
   369         return None
       
   370 
       
   371     def GetChildByName(self, Name):
       
   372         if Name:
       
   373             toks = Name.split('.')
       
   374             return self._GetChildBySomething("Name", toks)
       
   375         else:
       
   376             return self
       
   377 
       
   378     def GetChildByIECLocation(self, Location):
       
   379         if Location:
       
   380             return self._GetChildBySomething("IEC_Channel", Location)
       
   381         else:
       
   382             return self
       
   383     
       
   384     def GetCurrentLocation(self):
       
   385         """
       
   386         @return:  Tupple containing confnode IEC location of current confnode : %I0.0.4.5 => (0,0,4,5)
       
   387         """
       
   388         return self.PlugParent.GetCurrentLocation() + (self.BaseParams.getIEC_Channel(),)
       
   389 
       
   390     def GetCurrentName(self):
       
   391         """
       
   392         @return:  String "ParentParentName.ParentName.Name"
       
   393         """
       
   394         return  self.PlugParent._GetCurrentName() + self.BaseParams.getName()
       
   395 
       
   396     def _GetCurrentName(self):
       
   397         """
       
   398         @return:  String "ParentParentName.ParentName.Name."
       
   399         """
       
   400         return  self.PlugParent._GetCurrentName() + self.BaseParams.getName() + "."
       
   401 
       
   402     def GetPlugRoot(self):
       
   403         return self.PlugParent.GetPlugRoot()
       
   404 
       
   405     def GetFullIEC_Channel(self):
       
   406         return ".".join([str(i) for i in self.GetCurrentLocation()]) + ".x"
       
   407 
       
   408     def GetLocations(self):
       
   409         location = self.GetCurrentLocation()
       
   410         return [loc for loc in self.PlugParent.GetLocations() if loc["LOC"][0:len(location)] == location]
       
   411 
       
   412     def GetVariableLocationTree(self):
       
   413         '''
       
   414         This function is meant to be overridden by confnodes.
       
   415 
       
   416         It should returns an list of dictionaries
       
   417         
       
   418         - IEC_type is an IEC type like BOOL/BYTE/SINT/...
       
   419         - location is a string of this variable's location, like "%IX0.0.0"
       
   420         '''
       
   421         children = []
       
   422         for child in self.IECSortedChilds():
       
   423             children.append(child.GetVariableLocationTree())
       
   424         return {"name": self.BaseParams.getName(),
       
   425                 "type": LOCATION_CONFNODE,
       
   426                 "location": self.GetFullIEC_Channel(),
       
   427                 "children": children}
       
   428 
       
   429     def FindNewName(self, DesiredName):
       
   430         """
       
   431         Changes Name to DesiredName if available, Name-N if not.
       
   432         @param DesiredName: The desired Name (string)
       
   433         """
       
   434         # Get Current Name
       
   435         CurrentName = self.BaseParams.getName()
       
   436         # Do nothing if no change
       
   437         #if CurrentName == DesiredName: return CurrentName
       
   438         # Build a list of used Name out of parent's PluggedChilds
       
   439         AllNames=[]
       
   440         for PlugInstance in self.PlugParent.IterChilds():
       
   441             if PlugInstance != self:
       
   442                 AllNames.append(PlugInstance.BaseParams.getName())
       
   443 
       
   444         # Find a free name, eventually appending digit
       
   445         res = DesiredName
       
   446         suffix = 1
       
   447         while res in AllNames:
       
   448             res = "%s-%d"%(DesiredName, suffix)
       
   449             suffix += 1
       
   450         
       
   451         # Get old path
       
   452         oldname = self.PlugPath()
       
   453         # Check previous confnode existance
       
   454         dontexist = self.BaseParams.getName() == "__unnamed__"
       
   455         # Set the new name
       
   456         self.BaseParams.setName(res)
       
   457         # Rename confnode dir if exist
       
   458         if not dontexist:
       
   459             shutil.move(oldname, self.PlugPath())
       
   460         # warn user he has two left hands
       
   461         if DesiredName != res:
       
   462             self.GetPlugRoot().logger.write_warning(_("A child names \"%s\" already exist -> \"%s\"\n")%(DesiredName,res))
       
   463         return res
       
   464 
       
   465     def GetAllChannels(self):
       
   466         AllChannels=[]
       
   467         for PlugInstance in self.PlugParent.IterChilds():
       
   468             if PlugInstance != self:
       
   469                 AllChannels.append(PlugInstance.BaseParams.getIEC_Channel())
       
   470         AllChannels.sort()
       
   471         return AllChannels
       
   472 
       
   473     def FindNewIEC_Channel(self, DesiredChannel):
       
   474         """
       
   475         Changes IEC Channel number to DesiredChannel if available, nearest available if not.
       
   476         @param DesiredChannel: The desired IEC channel (int)
       
   477         """
       
   478         # Get Current IEC channel
       
   479         CurrentChannel = self.BaseParams.getIEC_Channel()
       
   480         # Do nothing if no change
       
   481         #if CurrentChannel == DesiredChannel: return CurrentChannel
       
   482         # Build a list of used Channels out of parent's PluggedChilds
       
   483         AllChannels = self.GetAllChannels()
       
   484         
       
   485         # Now, try to guess the nearest available channel
       
   486         res = DesiredChannel
       
   487         while res in AllChannels: # While channel not free
       
   488             if res < CurrentChannel: # Want to go down ?
       
   489                 res -=  1 # Test for n-1
       
   490                 if res < 0 :
       
   491                     self.GetPlugRoot().logger.write_warning(_("Cannot find lower free IEC channel than %d\n")%CurrentChannel)
       
   492                     return CurrentChannel # Can't go bellow 0, do nothing
       
   493             else : # Want to go up ?
       
   494                 res +=  1 # Test for n-1
       
   495         # Finally set IEC Channel
       
   496         self.BaseParams.setIEC_Channel(res)
       
   497         return res
       
   498 
       
   499     def _OpenView(self, name=None):
       
   500         if self.EditorType is not None and self._View is None:
       
   501             app_frame = self.GetPlugRoot().AppFrame
       
   502             
       
   503             self._View = self.EditorType(app_frame.TabsOpened, self, app_frame)
       
   504             
       
   505             app_frame.EditProjectElement(self._View, self.PlugName())
       
   506             
       
   507             return self._View
       
   508         return None
       
   509 
       
   510     def OnCloseEditor(self, view):
       
   511         if self._View == view:
       
   512             self._View = None
       
   513 
       
   514     def OnPlugClose(self):
       
   515         if self._View is not None:
       
   516             app_frame = self.GetPlugRoot().AppFrame
       
   517             if app_frame is not None:
       
   518                 app_frame.DeletePage(self._View)
       
   519         return True
       
   520 
       
   521     def _doRemoveChild(self, PlugInstance):
       
   522         # Remove all childs of child
       
   523         for SubPlugInstance in PlugInstance.IterChilds():
       
   524             PlugInstance._doRemoveChild(SubPlugInstance)
       
   525         # Call the OnCloseMethod
       
   526         PlugInstance.OnPlugClose()
       
   527         # Delete confnode dir
       
   528         shutil.rmtree(PlugInstance.PlugPath())
       
   529         # Remove child of PluggedChilds
       
   530         self.PluggedChilds[PlugInstance.PlugType].remove(PlugInstance)
       
   531         # Forget it... (View have to refresh)
       
   532 
       
   533     def PlugRemove(self):
       
   534         # Fetch the confnode
       
   535         #PlugInstance = self.GetChildByName(PlugName)
       
   536         # Ask to his parent to remove it
       
   537         self.PlugParent._doRemoveChild(self)
       
   538 
       
   539     def PlugAddChild(self, PlugName, PlugType, IEC_Channel=0):
       
   540         """
       
   541         Create the confnodes that may be added as child to this node self
       
   542         @param PlugType: string desining the confnode class name (get name from PlugChildsTypes)
       
   543         @param PlugName: string for the name of the confnode instance
       
   544         """
       
   545         # reorgabize self.PlugChildsTypes tuples from (name, PlugClass, Help)
       
   546         # to ( name, (PlugClass, Help)), an make a dict
       
   547         transpose = zip(*self.PlugChildsTypes)
       
   548         PlugChildsTypes = dict(zip(transpose[0],zip(transpose[1],transpose[2])))
       
   549         # Check that adding this confnode is allowed
       
   550         try:
       
   551             PlugClass, PlugHelp = PlugChildsTypes[PlugType]
       
   552         except KeyError:
       
   553             raise Exception, _("Cannot create child %s of type %s ")%(PlugName, PlugType)
       
   554         
       
   555         # if PlugClass is a class factory, call it. (prevent unneeded imports)
       
   556         if type(PlugClass) == types.FunctionType:
       
   557             PlugClass = PlugClass()
       
   558         
       
   559         # Eventualy Initialize child instance list for this class of confnode
       
   560         PluggedChildsWithSameClass = self.PluggedChilds.setdefault(PlugType, list())
       
   561         # Check count
       
   562         if getattr(PlugClass, "PlugMaxCount", None) and len(PluggedChildsWithSameClass) >= PlugClass.PlugMaxCount:
       
   563             raise Exception, _("Max count (%d) reached for this confnode of type %s ")%(PlugClass.PlugMaxCount, PlugType)
       
   564         
       
   565         # create the final class, derived of provided confnode and template
       
   566         class FinalPlugClass(PlugClass, ConfigTreeNode):
       
   567             """
       
   568             ConfNode class is derivated into FinalPlugClass before being instanciated
       
   569             This way __init__ is overloaded to ensure ConfigTreeNode.__init__ is called 
       
   570             before PlugClass.__init__, and to do the file related stuff.
       
   571             """
       
   572             def __init__(_self):
       
   573                 # self is the parent
       
   574                 _self.PlugParent = self
       
   575                 # Keep track of the confnode type name
       
   576                 _self.PlugType = PlugType
       
   577                 # remind the help string, for more fancy display
       
   578                 _self.PlugHelp = PlugHelp
       
   579                 # Call the base confnode template init - change XSD into class members
       
   580                 ConfigTreeNode.__init__(_self)
       
   581                 # check name is unique
       
   582                 NewPlugName = _self.FindNewName(PlugName)
       
   583                 # If dir have already be made, and file exist
       
   584                 if os.path.isdir(_self.PlugPath(NewPlugName)): #and os.path.isfile(_self.ConfNodeXmlFilePath(PlugName)):
       
   585                     #Load the confnode.xml file into parameters members
       
   586                     _self.LoadXMLParams(NewPlugName)
       
   587                     # Basic check. Better to fail immediately.
       
   588                     if (_self.BaseParams.getName() != NewPlugName):
       
   589                         raise Exception, _("Project tree layout do not match confnode.xml %s!=%s ")%(NewPlugName, _self.BaseParams.getName())
       
   590 
       
   591                     # Now, self.PlugPath() should be OK
       
   592                     
       
   593                     # Check that IEC_Channel is not already in use.
       
   594                     _self.FindNewIEC_Channel(_self.BaseParams.getIEC_Channel())
       
   595                     # Call the confnode real __init__
       
   596                     if getattr(PlugClass, "__init__", None):
       
   597                         PlugClass.__init__(_self)
       
   598                     #Load and init all the childs
       
   599                     _self.LoadChilds()
       
   600                     #just loaded, nothing to saved
       
   601                     _self.ChangesToSave = False
       
   602                 else:
       
   603                     # If confnode do not have corresponding file/dirs - they will be created on Save
       
   604                     _self.PlugMakeDir()
       
   605                     # Find an IEC number
       
   606                     _self.FindNewIEC_Channel(IEC_Channel)
       
   607                     # Call the confnode real __init__
       
   608                     if getattr(PlugClass, "__init__", None):
       
   609                         PlugClass.__init__(_self)
       
   610                     _self.PlugRequestSave()
       
   611                     #just created, must be saved
       
   612                     _self.ChangesToSave = True
       
   613                 
       
   614             def _getBuildPath(_self):
       
   615                 return self._getBuildPath()
       
   616             
       
   617         # Create the object out of the resulting class
       
   618         newConfNodeOpj = FinalPlugClass()
       
   619         # Store it in PluggedChils
       
   620         PluggedChildsWithSameClass.append(newConfNodeOpj)
       
   621         
       
   622         return newConfNodeOpj
       
   623     
       
   624     def ClearPluggedChilds(self):
       
   625         for child in self.IterChilds():
       
   626             child.ClearPluggedChilds()
       
   627         self.PluggedChilds = {}
       
   628     
       
   629     def LoadSTLibrary(self):
       
   630         # Get library blocks if plcopen library exist
       
   631         library_path = self.ConfNodeLibraryFilePath()
       
   632         if os.path.isfile(library_path):
       
   633             self.LibraryControler = PLCControler()
       
   634             self.LibraryControler.OpenXMLFile(library_path)
       
   635             self.LibraryControler.ClearConfNodeTypes()
       
   636             self.LibraryControler.AddConfNodeTypesList(self.ParentsTypesFactory())
       
   637 
       
   638     def LoadXMLParams(self, PlugName = None):
       
   639         methode_name = os.path.join(self.PlugPath(PlugName), "methods.py")
       
   640         if os.path.isfile(methode_name):
       
   641             execfile(methode_name)
       
   642         
       
   643         # Get the base xml tree
       
   644         if self.MandatoryParams:
       
   645             try:
       
   646                 basexmlfile = open(self.ConfNodeBaseXmlFilePath(PlugName), 'r')
       
   647                 basetree = minidom.parse(basexmlfile)
       
   648                 self.MandatoryParams[1].loadXMLTree(basetree.childNodes[0])
       
   649                 basexmlfile.close()
       
   650             except Exception, exc:
       
   651                 self.GetPlugRoot().logger.write_error(_("Couldn't load confnode base parameters %s :\n %s") % (PlugName, str(exc)))
       
   652                 self.GetPlugRoot().logger.write_error(traceback.format_exc())
       
   653         
       
   654         # Get the xml tree
       
   655         if self.PlugParams:
       
   656             try:
       
   657                 xmlfile = open(self.ConfNodeXmlFilePath(PlugName), 'r')
       
   658                 tree = minidom.parse(xmlfile)
       
   659                 self.PlugParams[1].loadXMLTree(tree.childNodes[0])
       
   660                 xmlfile.close()
       
   661             except Exception, exc:
       
   662                 self.GetPlugRoot().logger.write_error(_("Couldn't load confnode parameters %s :\n %s") % (PlugName, str(exc)))
       
   663                 self.GetPlugRoot().logger.write_error(traceback.format_exc())
       
   664         
       
   665     def LoadChilds(self):
       
   666         # Iterate over all PlugName@PlugType in confnode directory, and try to open them
       
   667         for PlugDir in os.listdir(self.PlugPath()):
       
   668             if os.path.isdir(os.path.join(self.PlugPath(), PlugDir)) and \
       
   669                PlugDir.count(NameTypeSeparator) == 1:
       
   670                 pname, ptype = PlugDir.split(NameTypeSeparator)
       
   671                 try:
       
   672                     self.PlugAddChild(pname, ptype)
       
   673                 except Exception, exc:
       
   674                     self.GetPlugRoot().logger.write_error(_("Could not add child \"%s\", type %s :\n%s\n")%(pname, ptype, str(exc)))
       
   675                     self.GetPlugRoot().logger.write_error(traceback.format_exc())
       
   676 
       
   677     def EnableMethod(self, method, value):
       
   678         for d in self.ConfNodeMethods:
       
   679             if d["method"]==method:
       
   680                 d["enabled"]=value
       
   681                 return True
       
   682         return False
       
   683 
       
   684     def ShowMethod(self, method, value):
       
   685         for d in self.ConfNodeMethods:
       
   686             if d["method"]==method:
       
   687                 d["shown"]=value
       
   688                 return True
       
   689         return False
       
   690 
       
   691     def CallMethod(self, method):
       
   692         for d in self.ConfNodeMethods:
       
   693             if d["method"]==method and d.get("enabled", True) and d.get("shown", True):
       
   694                 getattr(self, method)()
       
   695 
       
   696 def _GetClassFunction(name):
       
   697     def GetRootClass():
       
   698         return getattr(__import__("confnodes." + name), name).RootClass
       
   699     return GetRootClass
       
   700 
       
   701 
       
   702 ####################################################################################
       
   703 ####################################################################################
       
   704 ####################################################################################
       
   705 ###################################   ROOT    ######################################
       
   706 ####################################################################################
       
   707 ####################################################################################
       
   708 ####################################################################################
       
   709 
       
   710 if wx.Platform == '__WXMSW__':
       
   711     exe_ext=".exe"
       
   712 else:
       
   713     exe_ext=""
       
   714 
       
   715 # import for project creation timestamping
       
   716 from threading import Timer, Lock, Thread, Semaphore
       
   717 from time import localtime
       
   718 from datetime import datetime
       
   719 # import necessary stuff from PLCOpenEditor
       
   720 from PLCOpenEditor import PLCOpenEditor, ProjectDialog
       
   721 from TextViewer import TextViewer
       
   722 from plcopen.structures import IEC_KEYWORDS, TypeHierarchy_list
       
   723 
       
   724 
       
   725 import re, tempfile
       
   726 import targets
       
   727 from targets.typemapping import DebugTypesSize
       
   728 
       
   729 import connectors
       
   730 from discovery import DiscoveryDialog
       
   731 from weakref import WeakKeyDictionary
       
   732 
       
   733 MATIEC_ERROR_MODEL = re.compile(".*\.st:(\d+)-(\d+)\.\.(\d+)-(\d+): error : (.*)$")
       
   734 
       
   735 DEBUG_RETRIES_WARN = 3
       
   736 DEBUG_RETRIES_REREGISTER = 4
       
   737 
       
   738 class ConfigTreeRoot(ConfigTreeNode, PLCControler):
       
   739     """
       
   740     This class define Root object of the confnode tree. 
       
   741     It is responsible of :
       
   742     - Managing project directory
       
   743     - Building project
       
   744     - Handling PLCOpenEditor controler and view
       
   745     - Loading user confnodes and instanciante them as childs
       
   746     - ...
       
   747     
       
   748     """
       
   749 
       
   750     # For root object, available Childs Types are modules of the confnode packages.
       
   751     PlugChildsTypes = [(name, _GetClassFunction(name), help) for name, help in zip(confnodes.__all__,confnodes.helps)]
       
   752 
       
   753     XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
       
   754     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
       
   755       <xsd:element name="BeremizRoot">
       
   756         <xsd:complexType>
       
   757           <xsd:sequence>
       
   758             <xsd:element name="TargetType">
       
   759               <xsd:complexType>
       
   760                 <xsd:choice minOccurs="0">
       
   761                 """+targets.targetchoices+"""
       
   762                 </xsd:choice>
       
   763               </xsd:complexType>
       
   764             </xsd:element>
       
   765           </xsd:sequence>
       
   766           <xsd:attribute name="URI_location" type="xsd:string" use="optional" default=""/>
       
   767           <xsd:attribute name="Enable_ConfNodes" type="xsd:boolean" use="optional" default="true"/>
       
   768         </xsd:complexType>
       
   769       </xsd:element>
       
   770     </xsd:schema>
       
   771     """
       
   772 
       
   773     def __init__(self, frame, logger):
       
   774         PLCControler.__init__(self)
       
   775 
       
   776         self.MandatoryParams = None
       
   777         self.SetAppFrame(frame, logger)
       
   778         self._builder = None
       
   779         self._connector = None
       
   780         
       
   781         self.iec2c_path = os.path.join(base_folder, "matiec", "iec2c"+exe_ext)
       
   782         self.ieclib_path = os.path.join(base_folder, "matiec", "lib")
       
   783         
       
   784         # Setup debug information
       
   785         self.IECdebug_datas = {}
       
   786         self.IECdebug_lock = Lock()
       
   787 
       
   788         self.DebugTimer=None
       
   789         self.ResetIECProgramsAndVariables()
       
   790         
       
   791         #This method are not called here... but in NewProject and OpenProject
       
   792         #self._AddParamsMembers()
       
   793         #self.PluggedChilds = {}
       
   794 
       
   795         # In both new or load scenario, no need to save
       
   796         self.ChangesToSave = False
       
   797         # root have no parent
       
   798         self.PlugParent = None
       
   799         # Keep track of the confnode type name
       
   800         self.PlugType = "Beremiz"
       
   801         self.PluggedChilds = {}
       
   802         # After __init__ root confnode is not valid
       
   803         self.ProjectPath = None
       
   804         self._setBuildPath(None)
       
   805         self.DebugThread = None
       
   806         self.debug_break = False
       
   807         self.previous_plcstate = None
       
   808         # copy ConfNodeMethods so that it can be later customized
       
   809         self.ConfNodeMethods = [dic.copy() for dic in self.ConfNodeMethods]
       
   810         self.LoadSTLibrary()
       
   811 
       
   812     def __del__(self):
       
   813         if self.DebugTimer:
       
   814             self.DebugTimer.cancel()
       
   815         self.KillDebugThread()
       
   816 
       
   817     def SetAppFrame(self, frame, logger):
       
   818         self.AppFrame = frame
       
   819         self.logger = logger
       
   820         self.StatusTimer = None
       
   821         
       
   822         if frame is not None:
       
   823             # Timer to pull PLC status
       
   824             ID_STATUSTIMER = wx.NewId()
       
   825             self.StatusTimer = wx.Timer(self.AppFrame, ID_STATUSTIMER)
       
   826             self.AppFrame.Bind(wx.EVT_TIMER, self.PullPLCStatusProc, self.StatusTimer)
       
   827         
       
   828             self.RefreshConfNodesBlockLists()
       
   829 
       
   830     def ResetAppFrame(self, logger):
       
   831         if self.AppFrame is not None:
       
   832             self.AppFrame.Unbind(wx.EVT_TIMER, self.StatusTimer)
       
   833             self.StatusTimer = None
       
   834             self.AppFrame = None
       
   835         
       
   836         self.logger = logger
       
   837 
       
   838     def ConfNodeLibraryFilePath(self):
       
   839         return os.path.join(os.path.split(__file__)[0], "pous.xml")
       
   840 
       
   841     def PlugTestModified(self):
       
   842          return self.ChangesToSave or not self.ProjectIsSaved()
       
   843 
       
   844     def PlugFullName(self):
       
   845         return ""
       
   846 
       
   847     def GetPlugRoot(self):
       
   848         return self
       
   849 
       
   850     def GetIECLibPath(self):
       
   851         return self.ieclib_path
       
   852     
       
   853     def GetIEC2cPath(self):
       
   854         return self.iec2c_path
       
   855     
       
   856     def GetCurrentLocation(self):
       
   857         return ()
       
   858 
       
   859     def GetCurrentName(self):
       
   860         return ""
       
   861     
       
   862     def _GetCurrentName(self):
       
   863         return ""
       
   864 
       
   865     def GetProjectPath(self):
       
   866         return self.ProjectPath
       
   867 
       
   868     def GetProjectName(self):
       
   869         return os.path.split(self.ProjectPath)[1]
       
   870     
       
   871     def GetDefaultTargetName(self):
       
   872         if wx.Platform == '__WXMSW__':
       
   873             return "Win32"
       
   874         else:
       
   875             return "Linux"
       
   876 
       
   877     def GetTarget(self):
       
   878         target = self.BeremizRoot.getTargetType()
       
   879         if target.getcontent() is None:
       
   880             target = self.Classes["BeremizRoot_TargetType"]()
       
   881             target_name = self.GetDefaultTargetName()
       
   882             target.setcontent({"name": target_name, "value": self.Classes["TargetType_%s"%target_name]()})
       
   883         return target
       
   884     
       
   885     def GetParamsAttributes(self, path = None):
       
   886         params = ConfigTreeNode.GetParamsAttributes(self, path)
       
   887         if params[0]["name"] == "BeremizRoot":
       
   888             for child in params[0]["children"]:
       
   889                 if child["name"] == "TargetType" and child["value"] == '':
       
   890                     child.update(self.GetTarget().getElementInfos("TargetType")) 
       
   891         return params
       
   892         
       
   893     def SetParamsAttribute(self, path, value):
       
   894         if path.startswith("BeremizRoot.TargetType.") and self.BeremizRoot.getTargetType().getcontent() is None:
       
   895             self.BeremizRoot.setTargetType(self.GetTarget())
       
   896         return ConfigTreeNode.SetParamsAttribute(self, path, value)
       
   897         
       
   898     # helper func to check project path write permission
       
   899     def CheckProjectPathPerm(self, dosave=True):
       
   900         if CheckPathPerm(self.ProjectPath):
       
   901             return True
       
   902         dialog = wx.MessageDialog(self.AppFrame, 
       
   903                     _('You must have permission to work on the project\nWork on a project copy ?'),
       
   904                     _('Error'), 
       
   905                     wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
       
   906         answer = dialog.ShowModal()
       
   907         dialog.Destroy()
       
   908         if answer == wx.ID_YES:
       
   909             if self.SaveProjectAs():
       
   910                 self.AppFrame.RefreshAll()
       
   911                 self.AppFrame.RefreshTitle()
       
   912                 self.AppFrame.RefreshFileMenu()
       
   913                 return True
       
   914         return False
       
   915     
       
   916     def NewProject(self, ProjectPath, BuildPath=None):
       
   917         """
       
   918         Create a new project in an empty folder
       
   919         @param ProjectPath: path of the folder where project have to be created
       
   920         @param PLCParams: properties of the PLCOpen program created
       
   921         """
       
   922         # Verify that chosen folder is empty
       
   923         if not os.path.isdir(ProjectPath) or len(os.listdir(ProjectPath)) > 0:
       
   924             return _("Chosen folder isn't empty. You can't use it for a new project!")
       
   925         
       
   926         dialog = ProjectDialog(self.AppFrame)
       
   927         if dialog.ShowModal() == wx.ID_OK:
       
   928             values = dialog.GetValues()
       
   929             values["creationDateTime"] = datetime(*localtime()[:6])
       
   930             dialog.Destroy()
       
   931         else:
       
   932             dialog.Destroy()
       
   933             return _("Project not created")
       
   934         
       
   935         # Create PLCOpen program
       
   936         self.CreateNewProject(values)
       
   937         # Change XSD into class members
       
   938         self._AddParamsMembers()
       
   939         self.PluggedChilds = {}
       
   940         # Keep track of the root confnode (i.e. project path)
       
   941         self.ProjectPath = ProjectPath
       
   942         self._setBuildPath(BuildPath)
       
   943         # get confnodes bloclist (is that usefull at project creation?)
       
   944         self.RefreshConfNodesBlockLists()
       
   945         # this will create files base XML files
       
   946         self.SaveProject()
       
   947         return None
       
   948         
       
   949     def LoadProject(self, ProjectPath, BuildPath=None):
       
   950         """
       
   951         Load a project contained in a folder
       
   952         @param ProjectPath: path of the project folder
       
   953         """
       
   954         if os.path.basename(ProjectPath) == "":
       
   955             ProjectPath = os.path.dirname(ProjectPath)
       
   956 		# Verify that project contains a PLCOpen program
       
   957         plc_file = os.path.join(ProjectPath, "plc.xml")
       
   958         if not os.path.isfile(plc_file):
       
   959             return _("Chosen folder doesn't contain a program. It's not a valid project!")
       
   960         # Load PLCOpen file
       
   961         result = self.OpenXMLFile(plc_file)
       
   962         if result:
       
   963             return result
       
   964         # Change XSD into class members
       
   965         self._AddParamsMembers()
       
   966         self.PluggedChilds = {}
       
   967         # Keep track of the root confnode (i.e. project path)
       
   968         self.ProjectPath = ProjectPath
       
   969         self._setBuildPath(BuildPath)
       
   970         # If dir have already be made, and file exist
       
   971         if os.path.isdir(self.PlugPath()) and os.path.isfile(self.ConfNodeXmlFilePath()):
       
   972             #Load the confnode.xml file into parameters members
       
   973             result = self.LoadXMLParams()
       
   974             if result:
       
   975                 return result
       
   976             #Load and init all the childs
       
   977             self.LoadChilds()
       
   978         self.RefreshConfNodesBlockLists()
       
   979         
       
   980         if os.path.exists(self._getBuildPath()):
       
   981             self.EnableMethod("_Clean", True)
       
   982 
       
   983         if os.path.isfile(self._getIECrawcodepath()):
       
   984             self.ShowMethod("_showIECcode", True)
       
   985 
       
   986         return None
       
   987     
       
   988     def CloseProject(self):
       
   989         self.ClearPluggedChilds()
       
   990         self.ResetAppFrame(None)
       
   991         
       
   992     def SaveProject(self):
       
   993         if self.CheckProjectPathPerm(False):
       
   994             self.SaveXMLFile(os.path.join(self.ProjectPath, 'plc.xml'))
       
   995             result = self.PlugRequestSave()
       
   996             if result:
       
   997                 self.logger.write_error(result)
       
   998     
       
   999     def SaveProjectAs(self, dosave=True):
       
  1000         # Ask user to choose a path with write permissions
       
  1001         if wx.Platform == '__WXMSW__':
       
  1002             path = os.getenv("USERPROFILE")
       
  1003         else:
       
  1004             path = os.getenv("HOME")
       
  1005         dirdialog = wx.DirDialog(self.AppFrame , _("Choose a directory to save project"), path, wx.DD_NEW_DIR_BUTTON)
       
  1006         answer = dirdialog.ShowModal()
       
  1007         dirdialog.Destroy()
       
  1008         if answer == wx.ID_OK:
       
  1009             newprojectpath = dirdialog.GetPath()
       
  1010             if os.path.isdir(newprojectpath):
       
  1011                 self.ProjectPath = newprojectpath
       
  1012                 if dosave:
       
  1013                     self.SaveProject()
       
  1014                 self._setBuildPath(self.BuildPath)
       
  1015                 return True
       
  1016         return False
       
  1017     
       
  1018     # Update PLCOpenEditor ConfNode Block types from loaded confnodes
       
  1019     def RefreshConfNodesBlockLists(self):
       
  1020         if getattr(self, "PluggedChilds", None) is not None:
       
  1021             self.ClearConfNodeTypes()
       
  1022             self.AddConfNodeTypesList(self.ConfNodesTypesFactory())
       
  1023         if self.AppFrame is not None:
       
  1024             self.AppFrame.RefreshLibraryPanel()
       
  1025             self.AppFrame.RefreshEditor()
       
  1026     
       
  1027     # Update a PLCOpenEditor Pou variable location
       
  1028     def UpdateProjectVariableLocation(self, old_leading, new_leading):
       
  1029         self.Project.updateElementAddress(old_leading, new_leading)
       
  1030         self.BufferProject()
       
  1031         if self.AppFrame is not None:
       
  1032             self.AppFrame.RefreshTitle()
       
  1033             self.AppFrame.RefreshInstancesTree()
       
  1034             self.AppFrame.RefreshFileMenu()
       
  1035             self.AppFrame.RefreshEditMenu()
       
  1036             self.AppFrame.RefreshEditor()
       
  1037     
       
  1038     def GetVariableLocationTree(self):
       
  1039         '''
       
  1040         This function is meant to be overridden by confnodes.
       
  1041 
       
  1042         It should returns an list of dictionaries
       
  1043         
       
  1044         - IEC_type is an IEC type like BOOL/BYTE/SINT/...
       
  1045         - location is a string of this variable's location, like "%IX0.0.0"
       
  1046         '''
       
  1047         children = []
       
  1048         for child in self.IECSortedChilds():
       
  1049             children.append(child.GetVariableLocationTree())
       
  1050         return children
       
  1051     
       
  1052     def ConfNodePath(self):
       
  1053         return os.path.join(os.path.split(__file__)[0], "confnodes")
       
  1054     
       
  1055     def PlugPath(self, PlugName=None):
       
  1056         return self.ProjectPath
       
  1057     
       
  1058     def ConfNodeXmlFilePath(self, PlugName=None):
       
  1059         return os.path.join(self.PlugPath(PlugName), "beremiz.xml")
       
  1060 
       
  1061     def ParentsTypesFactory(self):
       
  1062         return self.ConfNodeTypesFactory()
       
  1063 
       
  1064     def _setBuildPath(self, buildpath):
       
  1065         if CheckPathPerm(buildpath):
       
  1066             self.BuildPath = buildpath
       
  1067         else:
       
  1068             self.BuildPath = None
       
  1069         self.BuildPath = buildpath
       
  1070         self.DefaultBuildPath = None
       
  1071         if self._builder is not None:
       
  1072             self._builder.SetBuildPath(self._getBuildPath())
       
  1073 
       
  1074     def _getBuildPath(self):
       
  1075         # BuildPath is defined by user
       
  1076         if self.BuildPath is not None:
       
  1077             return self.BuildPath
       
  1078         # BuildPath isn't defined by user but already created by default
       
  1079         if self.DefaultBuildPath is not None:
       
  1080             return self.DefaultBuildPath
       
  1081         # Create a build path in project folder if user has permissions
       
  1082         if CheckPathPerm(self.ProjectPath):
       
  1083             self.DefaultBuildPath = os.path.join(self.ProjectPath, "build")
       
  1084         # Create a build path in temp folder
       
  1085         else:
       
  1086             self.DefaultBuildPath = os.path.join(tempfile.mkdtemp(), os.path.basename(self.ProjectPath), "build")
       
  1087             
       
  1088         if not os.path.exists(self.DefaultBuildPath):
       
  1089             os.makedirs(self.DefaultBuildPath)
       
  1090         return self.DefaultBuildPath
       
  1091     
       
  1092     def _getExtraFilesPath(self):
       
  1093         return os.path.join(self._getBuildPath(), "extra_files")
       
  1094 
       
  1095     def _getIECcodepath(self):
       
  1096         # define name for IEC code file
       
  1097         return os.path.join(self._getBuildPath(), "plc.st")
       
  1098     
       
  1099     def _getIECgeneratedcodepath(self):
       
  1100         # define name for IEC generated code file
       
  1101         return os.path.join(self._getBuildPath(), "generated_plc.st")
       
  1102     
       
  1103     def _getIECrawcodepath(self):
       
  1104         # define name for IEC raw code file
       
  1105         return os.path.join(self.PlugPath(), "raw_plc.st")
       
  1106     
       
  1107     def GetLocations(self):
       
  1108         locations = []
       
  1109         filepath = os.path.join(self._getBuildPath(),"LOCATED_VARIABLES.h")
       
  1110         if os.path.isfile(filepath):
       
  1111             # IEC2C compiler generate a list of located variables : LOCATED_VARIABLES.h
       
  1112             location_file = open(os.path.join(self._getBuildPath(),"LOCATED_VARIABLES.h"))
       
  1113             # each line of LOCATED_VARIABLES.h declares a located variable
       
  1114             lines = [line.strip() for line in location_file.readlines()]
       
  1115             # This regular expression parses the lines genereated by IEC2C
       
  1116             LOCATED_MODEL = re.compile("__LOCATED_VAR\((?P<IEC_TYPE>[A-Z]*),(?P<NAME>[_A-Za-z0-9]*),(?P<DIR>[QMI])(?:,(?P<SIZE>[XBWDL]))?,(?P<LOC>[,0-9]*)\)")
       
  1117             for line in lines:
       
  1118                 # If line match RE, 
       
  1119                 result = LOCATED_MODEL.match(line)
       
  1120                 if result:
       
  1121                     # Get the resulting dict
       
  1122                     resdict = result.groupdict()
       
  1123                     # rewrite string for variadic location as a tuple of integers
       
  1124                     resdict['LOC'] = tuple(map(int,resdict['LOC'].split(',')))
       
  1125                     # set located size to 'X' if not given 
       
  1126                     if not resdict['SIZE']:
       
  1127                         resdict['SIZE'] = 'X'
       
  1128                     # finally store into located variable list
       
  1129                     locations.append(resdict)
       
  1130         return locations
       
  1131         
       
  1132     def _Generate_SoftPLC(self):
       
  1133         """
       
  1134         Generate SoftPLC ST/IL/SFC code out of PLCOpenEditor controller, and compile it with IEC2C
       
  1135         @param buildpath: path where files should be created
       
  1136         """
       
  1137 
       
  1138         # Update PLCOpenEditor ConfNode Block types before generate ST code
       
  1139         self.RefreshConfNodesBlockLists()
       
  1140         
       
  1141         self.logger.write(_("Generating SoftPLC IEC-61131 ST/IL/SFC code...\n"))
       
  1142         buildpath = self._getBuildPath()
       
  1143         # ask PLCOpenEditor controller to write ST/IL/SFC code file
       
  1144         program, errors, warnings = self.GenerateProgram(self._getIECgeneratedcodepath())
       
  1145         if len(warnings) > 0:
       
  1146             self.logger.write_warning(_("Warnings in ST/IL/SFC code generator :\n"))
       
  1147             for warning in warnings:
       
  1148                 self.logger.write_warning("%s\n"%warning)
       
  1149         if len(errors) > 0:
       
  1150             # Failed !
       
  1151             self.logger.write_error(_("Error in ST/IL/SFC code generator :\n%s\n")%errors[0])
       
  1152             return False
       
  1153         plc_file = open(self._getIECcodepath(), "w")
       
  1154         # Add ST Library from confnodes
       
  1155         plc_file.write(self.ConfNodesSTLibraryFactory())
       
  1156         if os.path.isfile(self._getIECrawcodepath()):
       
  1157             plc_file.write(open(self._getIECrawcodepath(), "r").read())
       
  1158             plc_file.write("\n")
       
  1159         plc_file.close()
       
  1160         plc_file = open(self._getIECcodepath(), "r")
       
  1161         self.ProgramOffset = 0
       
  1162         for line in plc_file.xreadlines():
       
  1163             self.ProgramOffset += 1
       
  1164         plc_file.close()
       
  1165         plc_file = open(self._getIECcodepath(), "a")
       
  1166         plc_file.write(open(self._getIECgeneratedcodepath(), "r").read())
       
  1167         plc_file.close()
       
  1168 
       
  1169         self.logger.write(_("Compiling IEC Program into C code...\n"))
       
  1170 
       
  1171         # Now compile IEC code into many C files
       
  1172         # files are listed to stdout, and errors to stderr. 
       
  1173         status, result, err_result = ProcessLogger(
       
  1174                self.logger,
       
  1175                "\"%s\" -f -I \"%s\" -T \"%s\" \"%s\""%(
       
  1176                          self.iec2c_path,
       
  1177                          self.ieclib_path, 
       
  1178                          buildpath,
       
  1179                          self._getIECcodepath()),
       
  1180                no_stdout=True, no_stderr=True).spin()
       
  1181         if status:
       
  1182             # Failed !
       
  1183             
       
  1184             # parse iec2c's error message. if it contains a line number,
       
  1185             # then print those lines from the generated IEC file.
       
  1186             for err_line in err_result.split('\n'):
       
  1187                 self.logger.write_warning(err_line + "\n")
       
  1188 
       
  1189                 m_result = MATIEC_ERROR_MODEL.match(err_line)
       
  1190                 if m_result is not None:
       
  1191                     first_line, first_column, last_line, last_column, error = m_result.groups()
       
  1192                     first_line, last_line = int(first_line), int(last_line)
       
  1193                     
       
  1194                     last_section = None
       
  1195                     f = open(self._getIECcodepath())
       
  1196 
       
  1197                     for i, line in enumerate(f.readlines()):
       
  1198                         i = i + 1
       
  1199                         if line[0] not in '\t \r\n':
       
  1200                             last_section = line
       
  1201 
       
  1202                         if first_line <= i <= last_line:
       
  1203                             if last_section is not None:
       
  1204                                 self.logger.write_warning("In section: " + last_section)
       
  1205                                 last_section = None # only write section once
       
  1206                             self.logger.write_warning("%04d: %s" % (i, line))
       
  1207 
       
  1208                     f.close()
       
  1209             
       
  1210             self.logger.write_error(_("Error : IEC to C compiler returned %d\n")%status)
       
  1211             return False
       
  1212         
       
  1213         # Now extract C files of stdout
       
  1214         C_files = [ fname for fname in result.splitlines() if fname[-2:]==".c" or fname[-2:]==".C" ]
       
  1215         # remove those that are not to be compiled because included by others
       
  1216         C_files.remove("POUS.c")
       
  1217         if not C_files:
       
  1218             self.logger.write_error(_("Error : At least one configuration and one resource must be declared in PLC !\n"))
       
  1219             return False
       
  1220         # transform those base names to full names with path
       
  1221         C_files = map(lambda filename:os.path.join(buildpath, filename), C_files)
       
  1222         self.logger.write(_("Extracting Located Variables...\n"))
       
  1223         # Keep track of generated located variables for later use by self._Generate_C
       
  1224         self.PLCGeneratedLocatedVars = self.GetLocations()
       
  1225         # Keep track of generated C files for later use by self.PlugGenerate_C
       
  1226         self.PLCGeneratedCFiles = C_files
       
  1227         # compute CFLAGS for plc
       
  1228         self.plcCFLAGS = "\"-I"+self.ieclib_path+"\""
       
  1229         return True
       
  1230 
       
  1231     def GetBuilder(self):
       
  1232         """
       
  1233         Return a Builder (compile C code into machine code)
       
  1234         """
       
  1235         # Get target, module and class name
       
  1236         targetname = self.GetTarget().getcontent()["name"]
       
  1237         modulename = "targets." + targetname
       
  1238         classname = targetname + "_target"
       
  1239 
       
  1240         # Get module reference
       
  1241         try :
       
  1242             targetmodule = getattr(__import__(modulename), targetname)
       
  1243 
       
  1244         except Exception, msg:
       
  1245             self.logger.write_error(_("Can't find module for target %s!\n")%targetname)
       
  1246             self.logger.write_error(str(msg))
       
  1247             return None
       
  1248         
       
  1249         # Get target class
       
  1250         targetclass = getattr(targetmodule, classname)
       
  1251 
       
  1252         # if target already 
       
  1253         if self._builder is None or not isinstance(self._builder,targetclass):
       
  1254             # Get classname instance
       
  1255             self._builder = targetclass(self)
       
  1256         return self._builder
       
  1257 
       
  1258     def ResetBuildMD5(self):
       
  1259         builder=self.GetBuilder()
       
  1260         if builder is not None:
       
  1261             builder.ResetBinaryCodeMD5()
       
  1262         self.EnableMethod("_Transfer", False)
       
  1263 
       
  1264     def GetLastBuildMD5(self):
       
  1265         builder=self.GetBuilder()
       
  1266         if builder is not None:
       
  1267             return builder.GetBinaryCodeMD5()
       
  1268         else:
       
  1269             return None
       
  1270 
       
  1271     #######################################################################
       
  1272     #
       
  1273     #                C CODE GENERATION METHODS
       
  1274     #
       
  1275     #######################################################################
       
  1276     
       
  1277     def PlugGenerate_C(self, buildpath, locations):
       
  1278         """
       
  1279         Return C code generated by iec2c compiler 
       
  1280         when _generate_softPLC have been called
       
  1281         @param locations: ignored
       
  1282         @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND
       
  1283         """
       
  1284 
       
  1285         return ([(C_file_name, self.plcCFLAGS) 
       
  1286                 for C_file_name in self.PLCGeneratedCFiles ], 
       
  1287                "", # no ldflags
       
  1288                False) # do not expose retreive/publish calls
       
  1289     
       
  1290     def ResetIECProgramsAndVariables(self):
       
  1291         """
       
  1292         Reset variable and program list that are parsed from
       
  1293         CSV file generated by IEC2C compiler.
       
  1294         """
       
  1295         self._ProgramList = None
       
  1296         self._VariablesList = None
       
  1297         self._IECPathToIdx = {}
       
  1298         self._Ticktime = 0
       
  1299         self.TracedIECPath = []
       
  1300 
       
  1301     def GetIECProgramsAndVariables(self):
       
  1302         """
       
  1303         Parse CSV-like file  VARIABLES.csv resulting from IEC2C compiler.
       
  1304         Each section is marked with a line staring with '//'
       
  1305         list of all variables used in various POUs
       
  1306         """
       
  1307         if self._ProgramList is None or self._VariablesList is None:
       
  1308             try:
       
  1309                 csvfile = os.path.join(self._getBuildPath(),"VARIABLES.csv")
       
  1310                 # describes CSV columns
       
  1311                 ProgramsListAttributeName = ["num", "C_path", "type"]
       
  1312                 VariablesListAttributeName = ["num", "vartype", "IEC_path", "C_path", "type"]
       
  1313                 self._ProgramList = []
       
  1314                 self._VariablesList = []
       
  1315                 self._IECPathToIdx = {}
       
  1316                 
       
  1317                 # Separate sections
       
  1318                 ListGroup = []
       
  1319                 for line in open(csvfile,'r').xreadlines():
       
  1320                     strippedline = line.strip()
       
  1321                     if strippedline.startswith("//"):
       
  1322                         # Start new section
       
  1323                         ListGroup.append([])
       
  1324                     elif len(strippedline) > 0 and len(ListGroup) > 0:
       
  1325                         # append to this section
       
  1326                         ListGroup[-1].append(strippedline)
       
  1327         
       
  1328                 # first section contains programs
       
  1329                 for line in ListGroup[0]:
       
  1330                     # Split and Maps each field to dictionnary entries
       
  1331                     attrs = dict(zip(ProgramsListAttributeName,line.strip().split(';')))
       
  1332                     # Truncate "C_path" to remove conf an ressources names
       
  1333                     attrs["C_path"] = '__'.join(attrs["C_path"].split(".",2)[1:])
       
  1334                     # Push this dictionnary into result.
       
  1335                     self._ProgramList.append(attrs)
       
  1336         
       
  1337                 # second section contains all variables
       
  1338                 for line in ListGroup[1]:
       
  1339                     # Split and Maps each field to dictionnary entries
       
  1340                     attrs = dict(zip(VariablesListAttributeName,line.strip().split(';')))
       
  1341                     # Truncate "C_path" to remove conf an ressources names
       
  1342                     parts = attrs["C_path"].split(".",2)
       
  1343                     if len(parts) > 2:
       
  1344                         attrs["C_path"] = '__'.join(parts[1:])
       
  1345                     else:
       
  1346                         attrs["C_path"] = '__'.join(parts)
       
  1347                     # Push this dictionnary into result.
       
  1348                     self._VariablesList.append(attrs)
       
  1349                     # Fill in IEC<->C translation dicts
       
  1350                     IEC_path=attrs["IEC_path"]
       
  1351                     Idx=int(attrs["num"])
       
  1352                     self._IECPathToIdx[IEC_path]=(Idx, attrs["type"])
       
  1353                 
       
  1354                 # third section contains ticktime
       
  1355                 if len(ListGroup) > 2:
       
  1356                     self._Ticktime = int(ListGroup[2][0]) 
       
  1357                 
       
  1358             except Exception,e:
       
  1359                 self.logger.write_error(_("Cannot open/parse VARIABLES.csv!\n"))
       
  1360                 self.logger.write_error(traceback.format_exc())
       
  1361                 self.ResetIECProgramsAndVariables()
       
  1362                 return False
       
  1363 
       
  1364         return True
       
  1365 
       
  1366     def Generate_plc_debugger(self):
       
  1367         """
       
  1368         Generate trace/debug code out of PLC variable list
       
  1369         """
       
  1370         self.GetIECProgramsAndVariables()
       
  1371 
       
  1372         # prepare debug code
       
  1373         debug_code = targets.code("plc_debug") % {
       
  1374            "buffer_size": reduce(lambda x, y: x + y, [DebugTypesSize.get(v["type"], 0) for v in self._VariablesList], 0),
       
  1375            "programs_declarations":
       
  1376                "\n".join(["extern %(type)s %(C_path)s;"%p for p in self._ProgramList]),
       
  1377            "extern_variables_declarations":"\n".join([
       
  1378               {"EXT":"extern __IEC_%(type)s_p %(C_path)s;",
       
  1379                "IN":"extern __IEC_%(type)s_p %(C_path)s;",
       
  1380                "MEM":"extern __IEC_%(type)s_p %(C_path)s;",
       
  1381                "OUT":"extern __IEC_%(type)s_p %(C_path)s;",
       
  1382                "VAR":"extern __IEC_%(type)s_t %(C_path)s;"}[v["vartype"]]%v 
       
  1383                for v in self._VariablesList if v["vartype"] != "FB" and v["C_path"].find('.')<0]),
       
  1384            "for_each_variable_do_code":"\n".join([
       
  1385                {"EXT":"    (*fp)((void*)&%(C_path)s,%(type)s_P_ENUM);\n",
       
  1386                 "IN":"    (*fp)((void*)&%(C_path)s,%(type)s_P_ENUM);\n",
       
  1387                 "MEM":"    (*fp)((void*)&%(C_path)s,%(type)s_O_ENUM);\n",
       
  1388                 "OUT":"    (*fp)((void*)&%(C_path)s,%(type)s_O_ENUM);\n",
       
  1389                 "VAR":"    (*fp)((void*)&%(C_path)s,%(type)s_ENUM);\n"}[v["vartype"]]%v
       
  1390                 for v in self._VariablesList if v["vartype"] != "FB" and v["type"] in DebugTypesSize ]),
       
  1391            "find_variable_case_code":"\n".join([
       
  1392                "    case %(num)s:\n"%v+
       
  1393                "        *varp = (void*)&%(C_path)s;\n"%v+
       
  1394                {"EXT":"        return %(type)s_P_ENUM;\n",
       
  1395                 "IN":"        return %(type)s_P_ENUM;\n",
       
  1396                 "MEM":"        return %(type)s_O_ENUM;\n",
       
  1397                 "OUT":"        return %(type)s_O_ENUM;\n",
       
  1398                 "VAR":"        return %(type)s_ENUM;\n"}[v["vartype"]]%v
       
  1399                 for v in self._VariablesList if v["vartype"] != "FB" and v["type"] in DebugTypesSize ])}
       
  1400         
       
  1401         return debug_code
       
  1402         
       
  1403     def Generate_plc_common_main(self):
       
  1404         """
       
  1405         Use confnodes layout given in LocationCFilesAndCFLAGS to
       
  1406         generate glue code that dispatch calls to all confnodes
       
  1407         """
       
  1408         # filter location that are related to code that will be called
       
  1409         # in retreive, publish, init, cleanup
       
  1410         locstrs = map(lambda x:"_".join(map(str,x)),
       
  1411            [loc for loc,Cfiles,DoCalls in self.LocationCFilesAndCFLAGS if loc and DoCalls])
       
  1412 
       
  1413         # Generate main, based on template
       
  1414         if self.BeremizRoot.getEnable_ConfNodes():
       
  1415             plc_main_code = targets.code("plc_common_main") % {
       
  1416                 "calls_prototypes":"\n".join([(
       
  1417                       "int __init_%(s)s(int argc,char **argv);\n"+
       
  1418                       "void __cleanup_%(s)s(void);\n"+
       
  1419                       "void __retrieve_%(s)s(void);\n"+
       
  1420                       "void __publish_%(s)s(void);")%{'s':locstr} for locstr in locstrs]),
       
  1421                 "retrieve_calls":"\n    ".join([
       
  1422                       "__retrieve_%s();"%locstrs[i-1] for i in xrange(len(locstrs), 0, -1)]),
       
  1423                 "publish_calls":"\n    ".join([ #Call publish in reverse order
       
  1424                       "__publish_%s();"%locstr for locstr in locstrs]),
       
  1425                 "init_calls":"\n    ".join([
       
  1426                       "init_level=%d; "%(i+1)+
       
  1427                       "if((res = __init_%s(argc,argv))){"%locstr +
       
  1428                       #"printf(\"%s\"); "%locstr + #for debug
       
  1429                       "return res;}" for i,locstr in enumerate(locstrs)]),
       
  1430                 "cleanup_calls":"\n    ".join([
       
  1431                       "if(init_level >= %d) "%i+
       
  1432                       "__cleanup_%s();"%locstrs[i-1] for i in xrange(len(locstrs), 0, -1)])
       
  1433                 }
       
  1434         else:
       
  1435             plc_main_code = targets.code("plc_common_main") % {
       
  1436                 "calls_prototypes":"\n",
       
  1437                 "retrieve_calls":"\n",
       
  1438                 "publish_calls":"\n",
       
  1439                 "init_calls":"\n",
       
  1440                 "cleanup_calls":"\n"
       
  1441                 }
       
  1442         plc_main_code += targets.targetcode(self.GetTarget().getcontent()["name"])
       
  1443         return plc_main_code
       
  1444 
       
  1445         
       
  1446     def _Build(self):
       
  1447         """
       
  1448         Method called by user to (re)build SoftPLC and confnode tree
       
  1449         """
       
  1450         if self.AppFrame is not None:
       
  1451             self.AppFrame.ClearErrors()
       
  1452         
       
  1453         buildpath = self._getBuildPath()
       
  1454 
       
  1455         # Eventually create build dir
       
  1456         if not os.path.exists(buildpath):
       
  1457             os.mkdir(buildpath)
       
  1458         # There is something to clean
       
  1459         self.EnableMethod("_Clean", True)
       
  1460 
       
  1461         self.logger.flush()
       
  1462         self.logger.write(_("Start build in %s\n") % buildpath)
       
  1463 
       
  1464         # Generate SoftPLC IEC code
       
  1465         IECGenRes = self._Generate_SoftPLC()
       
  1466         self.ShowMethod("_showIECcode", True)
       
  1467 
       
  1468         # If IEC code gen fail, bail out.
       
  1469         if not IECGenRes:
       
  1470             self.logger.write_error(_("IEC-61131-3 code generation failed !\n"))
       
  1471             self.ResetBuildMD5()
       
  1472             return False
       
  1473 
       
  1474         # Reset variable and program list that are parsed from
       
  1475         # CSV file generated by IEC2C compiler.
       
  1476         self.ResetIECProgramsAndVariables()
       
  1477         
       
  1478         # Generate C code and compilation params from confnode hierarchy
       
  1479         self.logger.write(_("Generating confnodes C code\n"))
       
  1480         try:
       
  1481             self.LocationCFilesAndCFLAGS, self.LDFLAGS, ExtraFiles = self._Generate_C(
       
  1482                 buildpath, 
       
  1483                 self.PLCGeneratedLocatedVars)
       
  1484         except Exception, exc:
       
  1485             self.logger.write_error(_("ConfNodes code generation failed !\n"))
       
  1486             self.logger.write_error(traceback.format_exc())
       
  1487             self.ResetBuildMD5()
       
  1488             return False
       
  1489 
       
  1490         # Get temporary directory path
       
  1491         extrafilespath = self._getExtraFilesPath()
       
  1492         # Remove old directory
       
  1493         if os.path.exists(extrafilespath):
       
  1494             shutil.rmtree(extrafilespath)
       
  1495         # Recreate directory
       
  1496         os.mkdir(extrafilespath)
       
  1497         # Then write the files
       
  1498         for fname,fobject in ExtraFiles:
       
  1499             fpath = os.path.join(extrafilespath,fname)
       
  1500             open(fpath, "wb").write(fobject.read())
       
  1501         # Now we can forget ExtraFiles (will close files object)
       
  1502         del ExtraFiles
       
  1503         
       
  1504         # Template based part of C code generation
       
  1505         # files are stacked at the beginning, as files of confnode tree root
       
  1506         for generator, filename, name in [
       
  1507            # debugger code
       
  1508            (self.Generate_plc_debugger, "plc_debugger.c", "Debugger"),
       
  1509            # init/cleanup/retrieve/publish, run and align code
       
  1510            (self.Generate_plc_common_main,"plc_common_main.c","Common runtime")]:
       
  1511             try:
       
  1512                 # Do generate
       
  1513                 code = generator()
       
  1514                 if code is None:
       
  1515                      raise
       
  1516                 code_path = os.path.join(buildpath,filename)
       
  1517                 open(code_path, "w").write(code)
       
  1518                 # Insert this file as first file to be compiled at root confnode
       
  1519                 self.LocationCFilesAndCFLAGS[0][1].insert(0,(code_path, self.plcCFLAGS))
       
  1520             except Exception, exc:
       
  1521                 self.logger.write_error(name+_(" generation failed !\n"))
       
  1522                 self.logger.write_error(traceback.format_exc())
       
  1523                 self.ResetBuildMD5()
       
  1524                 return False
       
  1525 
       
  1526         self.logger.write(_("C code generated successfully.\n"))
       
  1527 
       
  1528         # Get current or fresh builder
       
  1529         builder = self.GetBuilder()
       
  1530         if builder is None:
       
  1531             self.logger.write_error(_("Fatal : cannot get builder.\n"))
       
  1532             self.ResetBuildMD5()
       
  1533             return False
       
  1534 
       
  1535         # Build
       
  1536         try:
       
  1537             if not builder.build() :
       
  1538                 self.logger.write_error(_("C Build failed.\n"))
       
  1539                 return False
       
  1540         except Exception, exc:
       
  1541             self.logger.write_error(_("C Build crashed !\n"))
       
  1542             self.logger.write_error(traceback.format_exc())
       
  1543             self.ResetBuildMD5()
       
  1544             return False
       
  1545 
       
  1546         self.logger.write(_("Successfully built.\n"))
       
  1547         # Update GUI status about need for transfer
       
  1548         self.CompareLocalAndRemotePLC()
       
  1549         return True
       
  1550     
       
  1551     def ShowError(self, logger, from_location, to_location):
       
  1552         chunk_infos = self.GetChunkInfos(from_location, to_location)
       
  1553         for infos, (start_row, start_col) in chunk_infos:
       
  1554             start = (from_location[0] - start_row, from_location[1] - start_col)
       
  1555             end = (to_location[0] - start_row, to_location[1] - start_col)
       
  1556             #print from_location, to_location, start_row, start_col, start, end
       
  1557             if self.AppFrame is not None:
       
  1558                 self.AppFrame.ShowError(infos, start, end)
       
  1559 
       
  1560     def _showIECcode(self):
       
  1561         self._OpenView("IEC code")
       
  1562 
       
  1563     def _editIECrawcode(self):
       
  1564         self._OpenView("IEC raw code")
       
  1565 
       
  1566     def _OpenView(self, name=None):
       
  1567         if name == "IEC code":
       
  1568             plc_file = self._getIECcodepath()
       
  1569         
       
  1570             IEC_code_viewer = TextViewer(self.AppFrame.TabsOpened, "", None, None, instancepath=name)
       
  1571             #IEC_code_viewer.Enable(False)
       
  1572             IEC_code_viewer.SetTextSyntax("ALL")
       
  1573             IEC_code_viewer.SetKeywords(IEC_KEYWORDS)
       
  1574             try:
       
  1575                 text = file(plc_file).read()
       
  1576             except:
       
  1577                 text = '(* No IEC code have been generated at that time ! *)'
       
  1578             IEC_code_viewer.SetText(text = text)
       
  1579             IEC_code_viewer.SetIcon(self.AppFrame.GenerateBitmap("ST"))
       
  1580                 
       
  1581             self.AppFrame.EditProjectElement(IEC_code_viewer, name)
       
  1582             
       
  1583             return IEC_code_viewer
       
  1584         
       
  1585         elif name == "IEC raw code":
       
  1586             controler = MiniTextControler(self._getIECrawcodepath())
       
  1587             IEC_raw_code_viewer = TextViewer(self.AppFrame.TabsOpened, "", None, controler, instancepath=name)
       
  1588             #IEC_raw_code_viewer.Enable(False)
       
  1589             IEC_raw_code_viewer.SetTextSyntax("ALL")
       
  1590             IEC_raw_code_viewer.SetKeywords(IEC_KEYWORDS)
       
  1591             IEC_raw_code_viewer.RefreshView()
       
  1592             IEC_raw_code_viewer.SetIcon(self.AppFrame.GenerateBitmap("ST"))
       
  1593                 
       
  1594             self.AppFrame.EditProjectElement(IEC_raw_code_viewer, name)
       
  1595 
       
  1596             return IEC_raw_code_viewer
       
  1597         
       
  1598         return None
       
  1599 
       
  1600     def _Clean(self):
       
  1601         if os.path.isdir(os.path.join(self._getBuildPath())):
       
  1602             self.logger.write(_("Cleaning the build directory\n"))
       
  1603             shutil.rmtree(os.path.join(self._getBuildPath()))
       
  1604         else:
       
  1605             self.logger.write_error(_("Build directory already clean\n"))
       
  1606         self.ShowMethod("_showIECcode", False)
       
  1607         self.EnableMethod("_Clean", False)
       
  1608         # kill the builder
       
  1609         self._builder = None
       
  1610         self.CompareLocalAndRemotePLC()
       
  1611 
       
  1612     ############# Real PLC object access #############
       
  1613     def UpdateMethodsFromPLCStatus(self):
       
  1614         # Get PLC state : Running or Stopped
       
  1615         # TODO : use explicit status instead of boolean
       
  1616         status = None
       
  1617         if self._connector is not None:
       
  1618             status = self._connector.GetPLCstatus()
       
  1619         if status is None:
       
  1620             self._connector = None
       
  1621             status = "Disconnected"
       
  1622         if(self.previous_plcstate != status):
       
  1623             for args in {
       
  1624                      "Started" :     [("_Run", False),
       
  1625                                       ("_Stop", True)],
       
  1626                      "Stopped" :     [("_Run", True),
       
  1627                                       ("_Stop", False)],
       
  1628                      "Empty" :       [("_Run", False),
       
  1629                                       ("_Stop", False)],
       
  1630                      "Broken" :      [],
       
  1631                      "Disconnected" :[("_Run", False),
       
  1632                                       ("_Stop", False),
       
  1633                                       ("_Transfer", False),
       
  1634                                       ("_Connect", True),
       
  1635                                       ("_Disconnect", False)],
       
  1636                    }.get(status,[]):
       
  1637                 self.ShowMethod(*args)
       
  1638             self.previous_plcstate = status
       
  1639             return True
       
  1640         return False
       
  1641     
       
  1642     def PullPLCStatusProc(self, event):
       
  1643         if self._connector is None:
       
  1644             self.StatusTimer.Stop()
       
  1645         if self.UpdateMethodsFromPLCStatus():
       
  1646             
       
  1647             status = _(self.previous_plcstate)
       
  1648             {"Broken": self.logger.write_error,
       
  1649              None: lambda x: None}.get(
       
  1650                 self.previous_plcstate, self.logger.write)(_("PLC is %s\n")%status)
       
  1651             self.AppFrame.RefreshAll()
       
  1652         
       
  1653     def RegisterDebugVarToConnector(self):
       
  1654         self.DebugTimer=None
       
  1655         Idxs = []
       
  1656         self.TracedIECPath = []
       
  1657         if self._connector is not None:
       
  1658             self.IECdebug_lock.acquire()
       
  1659             IECPathsToPop = []
       
  1660             for IECPath,data_tuple in self.IECdebug_datas.iteritems():
       
  1661                 WeakCallableDict, data_log, status, fvalue = data_tuple
       
  1662                 if len(WeakCallableDict) == 0:
       
  1663                     # Callable Dict is empty.
       
  1664                     # This variable is not needed anymore!
       
  1665                     #print "Unused : " + IECPath
       
  1666                     IECPathsToPop.append(IECPath)
       
  1667                 elif IECPath != "__tick__":
       
  1668                     # Convert 
       
  1669                     Idx, IEC_Type = self._IECPathToIdx.get(IECPath,(None,None))
       
  1670                     if Idx is not None:
       
  1671                         if IEC_Type in DebugTypesSize: 
       
  1672                             Idxs.append((Idx, IEC_Type, fvalue, IECPath))
       
  1673                         else:
       
  1674                             self.logger.write_warning(_("Debug : Unsuppoted type to debug %s\n")%IEC_Type)
       
  1675                     else:
       
  1676                         self.logger.write_warning(_("Debug : Unknown variable %s\n")%IECPath)
       
  1677             for IECPathToPop in IECPathsToPop:
       
  1678                 self.IECdebug_datas.pop(IECPathToPop)
       
  1679 
       
  1680             if Idxs:
       
  1681                 Idxs.sort()
       
  1682                 self.TracedIECPath = zip(*Idxs)[3]
       
  1683                 self._connector.SetTraceVariablesList(zip(*zip(*Idxs)[0:3]))
       
  1684             else:
       
  1685                 self.TracedIECPath = []
       
  1686                 self._connector.SetTraceVariablesList([])
       
  1687             self.IECdebug_lock.release()
       
  1688             
       
  1689             #for IEC_path, IECdebug_data in self.IECdebug_datas.iteritems():
       
  1690             #    print IEC_path, IECdebug_data[0].keys()
       
  1691 
       
  1692     def ReArmDebugRegisterTimer(self):
       
  1693         if self.DebugTimer is not None:
       
  1694             self.DebugTimer.cancel()
       
  1695 
       
  1696         # Timer to prevent rapid-fire when registering many variables
       
  1697         # use wx.CallAfter use keep using same thread. TODO : use wx.Timer instead
       
  1698         self.DebugTimer=Timer(0.5,wx.CallAfter,args = [self.RegisterDebugVarToConnector])
       
  1699         # Rearm anti-rapid-fire timer
       
  1700         self.DebugTimer.start()
       
  1701 
       
  1702     def GetDebugIECVariableType(self, IECPath):
       
  1703         Idx, IEC_Type = self._IECPathToIdx.get(IECPath,(None,None))
       
  1704         return IEC_Type
       
  1705         
       
  1706     def SubscribeDebugIECVariable(self, IECPath, callableobj, *args, **kwargs):
       
  1707         """
       
  1708         Dispatching use a dictionnary linking IEC variable paths
       
  1709         to a WeakKeyDictionary linking 
       
  1710         weakly referenced callables to optionnal args
       
  1711         """
       
  1712         if IECPath != "__tick__" and not self._IECPathToIdx.has_key(IECPath):
       
  1713             return None
       
  1714         
       
  1715         self.IECdebug_lock.acquire()
       
  1716         # If no entry exist, create a new one with a fresh WeakKeyDictionary
       
  1717         IECdebug_data = self.IECdebug_datas.get(IECPath, None)
       
  1718         if IECdebug_data is None:
       
  1719             IECdebug_data  = [
       
  1720                     WeakKeyDictionary(), # Callables
       
  1721                     [],                  # Data storage [(tick, data),...]
       
  1722                     "Registered",        # Variable status
       
  1723                     None]                # Forced value
       
  1724             self.IECdebug_datas[IECPath] = IECdebug_data
       
  1725         
       
  1726         IECdebug_data[0][callableobj]=(args, kwargs)
       
  1727 
       
  1728         self.IECdebug_lock.release()
       
  1729         
       
  1730         self.ReArmDebugRegisterTimer()
       
  1731         
       
  1732         return IECdebug_data[1]
       
  1733 
       
  1734     def UnsubscribeDebugIECVariable(self, IECPath, callableobj):
       
  1735         #print "Unsubscribe", IECPath, callableobj
       
  1736         self.IECdebug_lock.acquire()
       
  1737         IECdebug_data = self.IECdebug_datas.get(IECPath, None)
       
  1738         if IECdebug_data is not None:
       
  1739             IECdebug_data[0].pop(callableobj,None)
       
  1740         self.IECdebug_lock.release()
       
  1741 
       
  1742         self.ReArmDebugRegisterTimer()
       
  1743 
       
  1744     def UnsubscribeAllDebugIECVariable(self):
       
  1745         self.IECdebug_lock.acquire()
       
  1746         IECdebug_data = {}
       
  1747         self.IECdebug_lock.release()
       
  1748 
       
  1749         self.ReArmDebugRegisterTimer()
       
  1750 
       
  1751     def ForceDebugIECVariable(self, IECPath, fvalue):
       
  1752         if not self.IECdebug_datas.has_key(IECPath):
       
  1753             return
       
  1754         
       
  1755         self.IECdebug_lock.acquire()
       
  1756         
       
  1757         # If no entry exist, create a new one with a fresh WeakKeyDictionary
       
  1758         IECdebug_data = self.IECdebug_datas.get(IECPath, None)
       
  1759         IECdebug_data[2] = "Forced"
       
  1760         IECdebug_data[3] = fvalue
       
  1761         
       
  1762         self.IECdebug_lock.release()
       
  1763         
       
  1764         self.ReArmDebugRegisterTimer()
       
  1765     
       
  1766     def ReleaseDebugIECVariable(self, IECPath):
       
  1767         if not self.IECdebug_datas.has_key(IECPath):
       
  1768             return
       
  1769         
       
  1770         self.IECdebug_lock.acquire()
       
  1771         
       
  1772         # If no entry exist, create a new one with a fresh WeakKeyDictionary
       
  1773         IECdebug_data = self.IECdebug_datas.get(IECPath, None)
       
  1774         IECdebug_data[2] = "Registered"
       
  1775         IECdebug_data[3] = None
       
  1776         
       
  1777         self.IECdebug_lock.release()
       
  1778         
       
  1779         self.ReArmDebugRegisterTimer()
       
  1780     
       
  1781     def CallWeakcallables(self, IECPath, function_name, *cargs):
       
  1782         data_tuple = self.IECdebug_datas.get(IECPath, None)
       
  1783         if data_tuple is not None:
       
  1784             WeakCallableDict, data_log, status, fvalue = data_tuple
       
  1785             #data_log.append((debug_tick, value))
       
  1786             for weakcallable,(args,kwargs) in WeakCallableDict.iteritems():
       
  1787                 #print weakcallable, value, args, kwargs
       
  1788                 function = getattr(weakcallable, function_name, None)
       
  1789                 if function is not None:
       
  1790                     if status == "Forced" and cargs[1] == fvalue:
       
  1791                         function(*(cargs + (True,) + args), **kwargs)
       
  1792                     else:
       
  1793                         function(*(cargs + args), **kwargs)
       
  1794                 # This will block thread if more than one call is waiting
       
  1795 
       
  1796     def GetTicktime(self):
       
  1797         return self._Ticktime
       
  1798 
       
  1799     def RemoteExec(self, script, **kwargs):
       
  1800         if self._connector is None:
       
  1801             return -1, "No runtime connected!"
       
  1802         return self._connector.RemoteExec(script, **kwargs)
       
  1803 
       
  1804     def DebugThreadProc(self):
       
  1805         """
       
  1806         This thread waid PLC debug data, and dispatch them to subscribers
       
  1807         """
       
  1808         self.debug_break = False
       
  1809         debug_getvar_retry = 0
       
  1810         while (not self.debug_break) and (self._connector is not None):
       
  1811             Trace = self._connector.GetTraceVariables()
       
  1812             if(Trace):
       
  1813                 plc_status, debug_tick, debug_vars = Trace
       
  1814             else:
       
  1815                 plc_status = None
       
  1816             debug_getvar_retry += 1
       
  1817             #print debug_tick, debug_vars
       
  1818             if plc_status == "Started":
       
  1819                 self.IECdebug_lock.acquire()
       
  1820                 if len(debug_vars) == len(self.TracedIECPath):
       
  1821                     if debug_getvar_retry > DEBUG_RETRIES_WARN:
       
  1822                         self.logger.write(_("... debugger recovered\n"))
       
  1823                     debug_getvar_retry = 0
       
  1824                     for IECPath,value in zip(self.TracedIECPath, debug_vars):
       
  1825                         if value is not None:
       
  1826                             self.CallWeakcallables(IECPath, "NewValue", debug_tick, value)
       
  1827                     self.CallWeakcallables("__tick__", "NewDataAvailable")
       
  1828                 self.IECdebug_lock.release()
       
  1829                 if debug_getvar_retry == DEBUG_RETRIES_WARN:
       
  1830                     self.logger.write(_("Waiting debugger to recover...\n"))
       
  1831                 if debug_getvar_retry == DEBUG_RETRIES_REREGISTER:
       
  1832                     # re-register debug registry to PLC
       
  1833                     wx.CallAfter(self.RegisterDebugVarToConnector)
       
  1834                 if debug_getvar_retry != 0:
       
  1835                     # Be patient, tollerate PLC to come up before debugging
       
  1836                     time.sleep(0.1)
       
  1837             else:
       
  1838                 self.debug_break = True
       
  1839         self.logger.write(_("Debugger disabled\n"))
       
  1840         self.DebugThread = None
       
  1841 
       
  1842     def KillDebugThread(self):
       
  1843         tmp_debugthread = self.DebugThread
       
  1844         self.debug_break = True
       
  1845         if tmp_debugthread is not None:
       
  1846             self.logger.writeyield(_("Stopping debugger...\n"))
       
  1847             tmp_debugthread.join(timeout=5)
       
  1848             if tmp_debugthread.isAlive() and self.logger:
       
  1849                 self.logger.write_warning(_("Couldn't stop debugger.\n"))
       
  1850             else:
       
  1851                 self.logger.write(_("Debugger stopped.\n"))
       
  1852         self.DebugThread = None
       
  1853 
       
  1854     def _connect_debug(self): 
       
  1855         if self.AppFrame:
       
  1856             self.AppFrame.ResetGraphicViewers()
       
  1857         self.RegisterDebugVarToConnector()
       
  1858         if self.DebugThread is None:
       
  1859             self.DebugThread = Thread(target=self.DebugThreadProc)
       
  1860             self.DebugThread.start()
       
  1861     
       
  1862     def _Run(self):
       
  1863         """
       
  1864         Start PLC
       
  1865         """
       
  1866         if self.GetIECProgramsAndVariables():
       
  1867             self._connector.StartPLC()
       
  1868             self.logger.write(_("Starting PLC\n"))
       
  1869             self._connect_debug()
       
  1870         else:
       
  1871             self.logger.write_error(_("Couldn't start PLC !\n"))
       
  1872         wx.CallAfter(self.UpdateMethodsFromPLCStatus)
       
  1873        
       
  1874     def _Stop(self):
       
  1875         """
       
  1876         Stop PLC
       
  1877         """
       
  1878         if self._connector is not None and not self._connector.StopPLC():
       
  1879             self.logger.write_error(_("Couldn't stop PLC !\n"))
       
  1880 
       
  1881         # debugthread should die on his own
       
  1882         #self.KillDebugThread()
       
  1883         
       
  1884         wx.CallAfter(self.UpdateMethodsFromPLCStatus)
       
  1885 
       
  1886     def _Connect(self):
       
  1887         # don't accept re-connetion is already connected
       
  1888         if self._connector is not None:
       
  1889             self.logger.write_error(_("Already connected. Please disconnect\n"))
       
  1890             return
       
  1891         
       
  1892         # Get connector uri
       
  1893         uri = self.\
       
  1894               BeremizRoot.\
       
  1895               getURI_location().\
       
  1896               strip()
       
  1897 
       
  1898         # if uri is empty launch discovery dialog
       
  1899         if uri == "":
       
  1900             # Launch Service Discovery dialog
       
  1901             dialog = DiscoveryDialog(self.AppFrame)
       
  1902             answer = dialog.ShowModal()
       
  1903             uri = dialog.GetURI()
       
  1904             dialog.Destroy()
       
  1905             
       
  1906             # Nothing choosed or cancel button
       
  1907             if uri is None or answer == wx.ID_CANCEL:
       
  1908                 self.logger.write_error(_("Connection canceled!\n"))
       
  1909                 return
       
  1910             else:
       
  1911                 self.\
       
  1912                 BeremizRoot.\
       
  1913                 setURI_location(uri)
       
  1914        
       
  1915         # Get connector from uri
       
  1916         try:
       
  1917             self._connector = connectors.ConnectorFactory(uri, self)
       
  1918         except Exception, msg:
       
  1919             self.logger.write_error(_("Exception while connecting %s!\n")%uri)
       
  1920             self.logger.write_error(traceback.format_exc())
       
  1921 
       
  1922         # Did connection success ?
       
  1923         if self._connector is None:
       
  1924             # Oups.
       
  1925             self.logger.write_error(_("Connection failed to %s!\n")%uri)
       
  1926         else:
       
  1927             self.ShowMethod("_Connect", False)
       
  1928             self.ShowMethod("_Disconnect", True)
       
  1929             self.ShowMethod("_Transfer", True)
       
  1930 
       
  1931             self.CompareLocalAndRemotePLC()
       
  1932             
       
  1933             # Init with actual PLC status and print it
       
  1934             self.UpdateMethodsFromPLCStatus()
       
  1935             if self.previous_plcstate is not None:
       
  1936                 status = _(self.previous_plcstate)
       
  1937             else:
       
  1938                 status = ""
       
  1939             self.logger.write(_("PLC is %s\n")%status)
       
  1940             
       
  1941             # Start the status Timer
       
  1942             self.StatusTimer.Start(milliseconds=500, oneShot=False)
       
  1943             
       
  1944             if self.previous_plcstate=="Started":
       
  1945                 if self.DebugAvailable() and self.GetIECProgramsAndVariables():
       
  1946                     self.logger.write(_("Debug connect matching running PLC\n"))
       
  1947                     self._connect_debug()
       
  1948                 else:
       
  1949                     self.logger.write_warning(_("Debug do not match PLC - stop/transfert/start to re-enable\n"))
       
  1950 
       
  1951     def CompareLocalAndRemotePLC(self):
       
  1952         if self._connector is None:
       
  1953             return
       
  1954         # We are now connected. Update button status
       
  1955         MD5 = self.GetLastBuildMD5()
       
  1956         # Check remote target PLC correspondance to that md5
       
  1957         if MD5 is not None:
       
  1958             if not self._connector.MatchMD5(MD5):
       
  1959 #                self.logger.write_warning(
       
  1960 #                   _("Latest build does not match with target, please transfer.\n"))
       
  1961                 self.EnableMethod("_Transfer", True)
       
  1962             else:
       
  1963 #                self.logger.write(
       
  1964 #                   _("Latest build matches target, no transfer needed.\n"))
       
  1965                 self.EnableMethod("_Transfer", True)
       
  1966                 # warns controller that program match
       
  1967                 self.ProgramTransferred()
       
  1968                 #self.EnableMethod("_Transfer", False)
       
  1969         else:
       
  1970 #            self.logger.write_warning(
       
  1971 #                _("Cannot compare latest build to target. Please build.\n"))
       
  1972             self.EnableMethod("_Transfer", False)
       
  1973 
       
  1974 
       
  1975     def _Disconnect(self):
       
  1976         self._connector = None
       
  1977         self.StatusTimer.Stop()
       
  1978         wx.CallAfter(self.UpdateMethodsFromPLCStatus)
       
  1979         
       
  1980     def _Transfer(self):
       
  1981         # Get the last build PLC's 
       
  1982         MD5 = self.GetLastBuildMD5()
       
  1983         
       
  1984         # Check if md5 file is empty : ask user to build PLC 
       
  1985         if MD5 is None :
       
  1986             self.logger.write_error(_("Failed : Must build before transfer.\n"))
       
  1987             return False
       
  1988 
       
  1989         # Compare PLC project with PLC on target
       
  1990         if self._connector.MatchMD5(MD5):
       
  1991             self.logger.write(
       
  1992                 _("Latest build already matches current target. Transfering anyway...\n"))
       
  1993 
       
  1994         # Get temprary directory path
       
  1995         extrafilespath = self._getExtraFilesPath()
       
  1996         extrafiles = [(name, open(os.path.join(extrafilespath, name), 
       
  1997                                   'rb').read()) \
       
  1998                       for name in os.listdir(extrafilespath) \
       
  1999                       if not name=="CVS"]
       
  2000 
       
  2001         # Send PLC on target
       
  2002         builder = self.GetBuilder()
       
  2003         if builder is not None:
       
  2004             data = builder.GetBinaryCode()
       
  2005             if data is not None :
       
  2006                 if self._connector.NewPLC(MD5, data, extrafiles) and self.GetIECProgramsAndVariables():
       
  2007                     self.UnsubscribeAllDebugIECVariable()
       
  2008                     self.ProgramTransferred()
       
  2009                     if self.AppFrame is not None:
       
  2010                         self.AppFrame.RefreshInstancesTree()
       
  2011                         self.AppFrame.CloseObsoleteDebugTabs()
       
  2012                     self.logger.write(_("Transfer completed successfully.\n"))
       
  2013                 else:
       
  2014                     self.logger.write_error(_("Transfer failed\n"))
       
  2015             else:
       
  2016                 self.logger.write_error(_("No PLC to transfer (did build succeed ?)\n"))
       
  2017 
       
  2018         wx.CallAfter(self.UpdateMethodsFromPLCStatus)
       
  2019 
       
  2020     ConfNodeMethods = [
       
  2021         {"bitmap" : opjimg("Build"),
       
  2022          "name" : _("Build"),
       
  2023          "tooltip" : _("Build project into build folder"),
       
  2024          "method" : "_Build"},
       
  2025         {"bitmap" : opjimg("Clean"),
       
  2026          "name" : _("Clean"),
       
  2027          "enabled" : False,
       
  2028          "tooltip" : _("Clean project build folder"),
       
  2029          "method" : "_Clean"},
       
  2030         {"bitmap" : opjimg("Run"),
       
  2031          "name" : _("Run"),
       
  2032          "shown" : False,
       
  2033          "tooltip" : _("Start PLC"),
       
  2034          "method" : "_Run"},
       
  2035         {"bitmap" : opjimg("Stop"),
       
  2036          "name" : _("Stop"),
       
  2037          "shown" : False,
       
  2038          "tooltip" : _("Stop Running PLC"),
       
  2039          "method" : "_Stop"},
       
  2040         {"bitmap" : opjimg("Connect"),
       
  2041          "name" : _("Connect"),
       
  2042          "tooltip" : _("Connect to the target PLC"),
       
  2043          "method" : "_Connect"},
       
  2044         {"bitmap" : opjimg("Transfer"),
       
  2045          "name" : _("Transfer"),
       
  2046          "shown" : False,
       
  2047          "tooltip" : _("Transfer PLC"),
       
  2048          "method" : "_Transfer"},
       
  2049         {"bitmap" : opjimg("Disconnect"),
       
  2050          "name" : _("Disconnect"),
       
  2051          "shown" : False,
       
  2052          "tooltip" : _("Disconnect from PLC"),
       
  2053          "method" : "_Disconnect"},
       
  2054         {"bitmap" : opjimg("ShowIECcode"),
       
  2055          "name" : _("Show code"),
       
  2056          "shown" : False,
       
  2057          "tooltip" : _("Show IEC code generated by PLCGenerator"),
       
  2058          "method" : "_showIECcode"},
       
  2059         {"bitmap" : opjimg("editIECrawcode"),
       
  2060          "name" : _("Raw IEC code"),
       
  2061          "tooltip" : _("Edit raw IEC code added to code generated by PLCGenerator"),
       
  2062          "method" : "_editIECrawcode"},
       
  2063     ]