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