ProjectController.py
changeset 725 31dade089db5
parent 722 a94f361fc42e
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 shutil
       
     9 import wx
       
    10 import re, tempfile
       
    11 from threading import Timer, Lock, Thread
       
    12 from time import localtime
       
    13 from datetime import datetime
       
    14 from weakref import WeakKeyDictionary
       
    15 
       
    16 import targets
       
    17 import connectors
       
    18 from util import MiniTextControler, opjimg, CheckPathPerm, GetClassImporter
       
    19 from ProcessLogger import ProcessLogger
       
    20 from PLCControler import PLCControler 
       
    21 from PLCOpenEditor import ProjectDialog
       
    22 from TextViewer import TextViewer
       
    23 from plcopen.structures import IEC_KEYWORDS
       
    24 from targets.typemapping import DebugTypesSize
       
    25 from discovery import DiscoveryDialog
       
    26 from ConfigTreeNode import ConfigTreeNode
       
    27 
       
    28 base_folder = os.path.split(sys.path[0])[0]
       
    29 
       
    30 MATIEC_ERROR_MODEL = re.compile(".*\.st:(\d+)-(\d+)\.\.(\d+)-(\d+): error : (.*)$")
       
    31 
       
    32 DEBUG_RETRIES_WARN = 3
       
    33 DEBUG_RETRIES_REREGISTER = 4
       
    34 
       
    35 class ProjectController(ConfigTreeNode, PLCControler):
       
    36     """
       
    37     This class define Root object of the confnode tree. 
       
    38     It is responsible of :
       
    39     - Managing project directory
       
    40     - Building project
       
    41     - Handling PLCOpenEditor controler and view
       
    42     - Loading user confnodes and instanciante them as children
       
    43     - ...
       
    44     
       
    45     """
       
    46 
       
    47     # For root object, available Children Types are modules of the confnode packages.
       
    48     CTNChildrenTypes =  [(n, GetClassImporter(c), d) for n,d,h,c in features.catalog]
       
    49 
       
    50     XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
       
    51     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
       
    52       <xsd:element name="BeremizRoot">
       
    53         <xsd:complexType>
       
    54           <xsd:sequence>
       
    55             <xsd:element name="TargetType">
       
    56               <xsd:complexType>
       
    57                 <xsd:choice minOccurs="0">
       
    58                 """+targets.targetchoices+"""
       
    59                 </xsd:choice>
       
    60               </xsd:complexType>
       
    61             </xsd:element>
       
    62           </xsd:sequence>
       
    63           <xsd:attribute name="URI_location" type="xsd:string" use="optional" default=""/>
       
    64           <xsd:attribute name="Enable_ConfNodes" type="xsd:boolean" use="optional" default="true"/>
       
    65         </xsd:complexType>
       
    66       </xsd:element>
       
    67     </xsd:schema>
       
    68     """
       
    69 
       
    70     def __init__(self, frame, logger):
       
    71         PLCControler.__init__(self)
       
    72 
       
    73         self.MandatoryParams = None
       
    74         self.SetAppFrame(frame, logger)
       
    75         self._builder = None
       
    76         self._connector = None
       
    77         
       
    78         self.iec2c_path = os.path.join(base_folder, "matiec", "iec2c"+(".exe" if wx.Platform == '__WXMSW__' else ""))
       
    79         self.ieclib_path = os.path.join(base_folder, "matiec", "lib")
       
    80         
       
    81         # Setup debug information
       
    82         self.IECdebug_datas = {}
       
    83         self.IECdebug_lock = Lock()
       
    84 
       
    85         self.DebugTimer=None
       
    86         self.ResetIECProgramsAndVariables()
       
    87         
       
    88         #This method are not called here... but in NewProject and OpenProject
       
    89         #self._AddParamsMembers()
       
    90         #self.Children = {}
       
    91 
       
    92         # In both new or load scenario, no need to save
       
    93         self.ChangesToSave = False
       
    94         # root have no parent
       
    95         self.CTNParent = None
       
    96         # Keep track of the confnode type name
       
    97         self.CTNType = "Beremiz"
       
    98         self.Children = {}
       
    99         # After __init__ root confnode is not valid
       
   100         self.ProjectPath = None
       
   101         self._setBuildPath(None)
       
   102         self.DebugThread = None
       
   103         self.debug_break = False
       
   104         self.previous_plcstate = None
       
   105         # copy ConfNodeMethods so that it can be later customized
       
   106         self.ConfNodeMethods = [dic.copy() for dic in self.ConfNodeMethods]
       
   107         self.LoadSTLibrary()
       
   108 
       
   109     def __del__(self):
       
   110         if self.DebugTimer:
       
   111             self.DebugTimer.cancel()
       
   112         self.KillDebugThread()
       
   113 
       
   114     def SetAppFrame(self, frame, logger):
       
   115         self.AppFrame = frame
       
   116         self.logger = logger
       
   117         self.StatusTimer = None
       
   118         
       
   119         if frame is not None:
       
   120             # Timer to pull PLC status
       
   121             ID_STATUSTIMER = wx.NewId()
       
   122             self.StatusTimer = wx.Timer(self.AppFrame, ID_STATUSTIMER)
       
   123             self.AppFrame.Bind(wx.EVT_TIMER, self.PullPLCStatusProc, self.StatusTimer)
       
   124         
       
   125             self.RefreshConfNodesBlockLists()
       
   126 
       
   127     def ResetAppFrame(self, logger):
       
   128         if self.AppFrame is not None:
       
   129             self.AppFrame.Unbind(wx.EVT_TIMER, self.StatusTimer)
       
   130             self.StatusTimer = None
       
   131             self.AppFrame = None
       
   132         
       
   133         self.logger = logger
       
   134 
       
   135     def ConfNodeLibraryFilePath(self):
       
   136         return os.path.join(os.path.split(__file__)[0], "pous.xml")
       
   137 
       
   138     def CTNTestModified(self):
       
   139          return self.ChangesToSave or not self.ProjectIsSaved()
       
   140 
       
   141     def CTNFullName(self):
       
   142         return ""
       
   143 
       
   144     def GetCTRoot(self):
       
   145         return self
       
   146 
       
   147     def GetIECLibPath(self):
       
   148         return self.ieclib_path
       
   149     
       
   150     def GetIEC2cPath(self):
       
   151         return self.iec2c_path
       
   152     
       
   153     def GetCurrentLocation(self):
       
   154         return ()
       
   155 
       
   156     def GetCurrentName(self):
       
   157         return ""
       
   158     
       
   159     def _GetCurrentName(self):
       
   160         return ""
       
   161 
       
   162     def GetProjectPath(self):
       
   163         return self.ProjectPath
       
   164 
       
   165     def GetProjectName(self):
       
   166         return os.path.split(self.ProjectPath)[1]
       
   167     
       
   168     def GetDefaultTargetName(self):
       
   169         if wx.Platform == '__WXMSW__':
       
   170             return "Win32"
       
   171         else:
       
   172             return "Linux"
       
   173 
       
   174     def GetTarget(self):
       
   175         target = self.BeremizRoot.getTargetType()
       
   176         if target.getcontent() is None:
       
   177             target = self.Classes["BeremizRoot_TargetType"]()
       
   178             target_name = self.GetDefaultTargetName()
       
   179             target.setcontent({"name": target_name, "value": self.Classes["TargetType_%s"%target_name]()})
       
   180         return target
       
   181     
       
   182     def GetParamsAttributes(self, path = None):
       
   183         params = ConfigTreeNode.GetParamsAttributes(self, path)
       
   184         if params[0]["name"] == "BeremizRoot":
       
   185             for child in params[0]["children"]:
       
   186                 if child["name"] == "TargetType" and child["value"] == '':
       
   187                     child.update(self.GetTarget().getElementInfos("TargetType")) 
       
   188         return params
       
   189         
       
   190     def SetParamsAttribute(self, path, value):
       
   191         if path.startswith("BeremizRoot.TargetType.") and self.BeremizRoot.getTargetType().getcontent() is None:
       
   192             self.BeremizRoot.setTargetType(self.GetTarget())
       
   193         return ConfigTreeNode.SetParamsAttribute(self, path, value)
       
   194         
       
   195     # helper func to check project path write permission
       
   196     def CheckProjectPathPerm(self, dosave=True):
       
   197         if CheckPathPerm(self.ProjectPath):
       
   198             return True
       
   199         dialog = wx.MessageDialog(self.AppFrame, 
       
   200                     _('You must have permission to work on the project\nWork on a project copy ?'),
       
   201                     _('Error'), 
       
   202                     wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
       
   203         answer = dialog.ShowModal()
       
   204         dialog.Destroy()
       
   205         if answer == wx.ID_YES:
       
   206             if self.SaveProjectAs():
       
   207                 self.AppFrame.RefreshAll()
       
   208                 self.AppFrame.RefreshTitle()
       
   209                 self.AppFrame.RefreshFileMenu()
       
   210                 return True
       
   211         return False
       
   212     
       
   213     def NewProject(self, ProjectPath, BuildPath=None):
       
   214         """
       
   215         Create a new project in an empty folder
       
   216         @param ProjectPath: path of the folder where project have to be created
       
   217         @param PLCParams: properties of the PLCOpen program created
       
   218         """
       
   219         # Verify that chosen folder is empty
       
   220         if not os.path.isdir(ProjectPath) or len(os.listdir(ProjectPath)) > 0:
       
   221             return _("Chosen folder isn't empty. You can't use it for a new project!")
       
   222         
       
   223         dialog = ProjectDialog(self.AppFrame)
       
   224         if dialog.ShowModal() == wx.ID_OK:
       
   225             values = dialog.GetValues()
       
   226             values["creationDateTime"] = datetime(*localtime()[:6])
       
   227             dialog.Destroy()
       
   228         else:
       
   229             dialog.Destroy()
       
   230             return _("Project not created")
       
   231         
       
   232         # Create PLCOpen program
       
   233         self.CreateNewProject(values)
       
   234         # Change XSD into class members
       
   235         self._AddParamsMembers()
       
   236         self.Children = {}
       
   237         # Keep track of the root confnode (i.e. project path)
       
   238         self.ProjectPath = ProjectPath
       
   239         self._setBuildPath(BuildPath)
       
   240         # get confnodes bloclist (is that usefull at project creation?)
       
   241         self.RefreshConfNodesBlockLists()
       
   242         # this will create files base XML files
       
   243         self.SaveProject()
       
   244         return None
       
   245         
       
   246     def LoadProject(self, ProjectPath, BuildPath=None):
       
   247         """
       
   248         Load a project contained in a folder
       
   249         @param ProjectPath: path of the project folder
       
   250         """
       
   251         if os.path.basename(ProjectPath) == "":
       
   252             ProjectPath = os.path.dirname(ProjectPath)
       
   253 		# Verify that project contains a PLCOpen program
       
   254         plc_file = os.path.join(ProjectPath, "plc.xml")
       
   255         if not os.path.isfile(plc_file):
       
   256             return _("Chosen folder doesn't contain a program. It's not a valid project!")
       
   257         # Load PLCOpen file
       
   258         result = self.OpenXMLFile(plc_file)
       
   259         if result:
       
   260             return result
       
   261         # Change XSD into class members
       
   262         self._AddParamsMembers()
       
   263         self.Children = {}
       
   264         # Keep track of the root confnode (i.e. project path)
       
   265         self.ProjectPath = ProjectPath
       
   266         self._setBuildPath(BuildPath)
       
   267         # If dir have already be made, and file exist
       
   268         if os.path.isdir(self.CTNPath()) and os.path.isfile(self.ConfNodeXmlFilePath()):
       
   269             #Load the confnode.xml file into parameters members
       
   270             result = self.LoadXMLParams()
       
   271             if result:
       
   272                 return result
       
   273             #Load and init all the children
       
   274             self.LoadChildren()
       
   275         self.RefreshConfNodesBlockLists()
       
   276         
       
   277         if os.path.exists(self._getBuildPath()):
       
   278             self.EnableMethod("_Clean", True)
       
   279 
       
   280         if os.path.isfile(self._getIECrawcodepath()):
       
   281             self.ShowMethod("_showIECcode", True)
       
   282 
       
   283         return None
       
   284     
       
   285     def CloseProject(self):
       
   286         self.ClearChildren()
       
   287         self.ResetAppFrame(None)
       
   288         
       
   289     def SaveProject(self):
       
   290         if self.CheckProjectPathPerm(False):
       
   291             self.SaveXMLFile(os.path.join(self.ProjectPath, 'plc.xml'))
       
   292             result = self.CTNRequestSave()
       
   293             if result:
       
   294                 self.logger.write_error(result)
       
   295     
       
   296     def SaveProjectAs(self, dosave=True):
       
   297         # Ask user to choose a path with write permissions
       
   298         if wx.Platform == '__WXMSW__':
       
   299             path = os.getenv("USERPROFILE")
       
   300         else:
       
   301             path = os.getenv("HOME")
       
   302         dirdialog = wx.DirDialog(self.AppFrame , _("Choose a directory to save project"), path, wx.DD_NEW_DIR_BUTTON)
       
   303         answer = dirdialog.ShowModal()
       
   304         dirdialog.Destroy()
       
   305         if answer == wx.ID_OK:
       
   306             newprojectpath = dirdialog.GetPath()
       
   307             if os.path.isdir(newprojectpath):
       
   308                 self.ProjectPath = newprojectpath
       
   309                 if dosave:
       
   310                     self.SaveProject()
       
   311                 self._setBuildPath(self.BuildPath)
       
   312                 return True
       
   313         return False
       
   314     
       
   315     # Update PLCOpenEditor ConfNode Block types from loaded confnodes
       
   316     def RefreshConfNodesBlockLists(self):
       
   317         if getattr(self, "Children", None) is not None:
       
   318             self.ClearConfNodeTypes()
       
   319             self.AddConfNodeTypesList(self.ConfNodesTypesFactory())
       
   320         if self.AppFrame is not None:
       
   321             self.AppFrame.RefreshLibraryPanel()
       
   322             self.AppFrame.RefreshEditor()
       
   323     
       
   324     # Update a PLCOpenEditor Pou variable location
       
   325     def UpdateProjectVariableLocation(self, old_leading, new_leading):
       
   326         self.Project.updateElementAddress(old_leading, new_leading)
       
   327         self.BufferProject()
       
   328         if self.AppFrame is not None:
       
   329             self.AppFrame.RefreshTitle()
       
   330             self.AppFrame.RefreshInstancesTree()
       
   331             self.AppFrame.RefreshFileMenu()
       
   332             self.AppFrame.RefreshEditMenu()
       
   333             self.AppFrame.RefreshEditor()
       
   334     
       
   335     def GetVariableLocationTree(self):
       
   336         '''
       
   337         This function is meant to be overridden by confnodes.
       
   338 
       
   339         It should returns an list of dictionaries
       
   340         
       
   341         - IEC_type is an IEC type like BOOL/BYTE/SINT/...
       
   342         - location is a string of this variable's location, like "%IX0.0.0"
       
   343         '''
       
   344         children = []
       
   345         for child in self.IECSortedChildren():
       
   346             children.append(child.GetVariableLocationTree())
       
   347         return children
       
   348     
       
   349     def ConfNodePath(self):
       
   350         return os.path.split(__file__)[0]
       
   351     
       
   352     def CTNPath(self, CTNName=None):
       
   353         return self.ProjectPath
       
   354     
       
   355     def ConfNodeXmlFilePath(self, CTNName=None):
       
   356         return os.path.join(self.CTNPath(CTNName), "beremiz.xml")
       
   357 
       
   358     def ParentsTypesFactory(self):
       
   359         return self.ConfNodeTypesFactory()
       
   360 
       
   361     def _setBuildPath(self, buildpath):
       
   362         if CheckPathPerm(buildpath):
       
   363             self.BuildPath = buildpath
       
   364         else:
       
   365             self.BuildPath = None
       
   366         self.BuildPath = buildpath
       
   367         self.DefaultBuildPath = None
       
   368         if self._builder is not None:
       
   369             self._builder.SetBuildPath(self._getBuildPath())
       
   370 
       
   371     def _getBuildPath(self):
       
   372         # BuildPath is defined by user
       
   373         if self.BuildPath is not None:
       
   374             return self.BuildPath
       
   375         # BuildPath isn't defined by user but already created by default
       
   376         if self.DefaultBuildPath is not None:
       
   377             return self.DefaultBuildPath
       
   378         # Create a build path in project folder if user has permissions
       
   379         if CheckPathPerm(self.ProjectPath):
       
   380             self.DefaultBuildPath = os.path.join(self.ProjectPath, "build")
       
   381         # Create a build path in temp folder
       
   382         else:
       
   383             self.DefaultBuildPath = os.path.join(tempfile.mkdtemp(), os.path.basename(self.ProjectPath), "build")
       
   384             
       
   385         if not os.path.exists(self.DefaultBuildPath):
       
   386             os.makedirs(self.DefaultBuildPath)
       
   387         return self.DefaultBuildPath
       
   388     
       
   389     def _getExtraFilesPath(self):
       
   390         return os.path.join(self._getBuildPath(), "extra_files")
       
   391 
       
   392     def _getIECcodepath(self):
       
   393         # define name for IEC code file
       
   394         return os.path.join(self._getBuildPath(), "plc.st")
       
   395     
       
   396     def _getIECgeneratedcodepath(self):
       
   397         # define name for IEC generated code file
       
   398         return os.path.join(self._getBuildPath(), "generated_plc.st")
       
   399     
       
   400     def _getIECrawcodepath(self):
       
   401         # define name for IEC raw code file
       
   402         return os.path.join(self.CTNPath(), "raw_plc.st")
       
   403     
       
   404     def GetLocations(self):
       
   405         locations = []
       
   406         filepath = os.path.join(self._getBuildPath(),"LOCATED_VARIABLES.h")
       
   407         if os.path.isfile(filepath):
       
   408             # IEC2C compiler generate a list of located variables : LOCATED_VARIABLES.h
       
   409             location_file = open(os.path.join(self._getBuildPath(),"LOCATED_VARIABLES.h"))
       
   410             # each line of LOCATED_VARIABLES.h declares a located variable
       
   411             lines = [line.strip() for line in location_file.readlines()]
       
   412             # This regular expression parses the lines genereated by IEC2C
       
   413             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]*)\)")
       
   414             for line in lines:
       
   415                 # If line match RE, 
       
   416                 result = LOCATED_MODEL.match(line)
       
   417                 if result:
       
   418                     # Get the resulting dict
       
   419                     resdict = result.groupdict()
       
   420                     # rewrite string for variadic location as a tuple of integers
       
   421                     resdict['LOC'] = tuple(map(int,resdict['LOC'].split(',')))
       
   422                     # set located size to 'X' if not given 
       
   423                     if not resdict['SIZE']:
       
   424                         resdict['SIZE'] = 'X'
       
   425                     # finally store into located variable list
       
   426                     locations.append(resdict)
       
   427         return locations
       
   428         
       
   429     def _Generate_SoftPLC(self):
       
   430         """
       
   431         Generate SoftPLC ST/IL/SFC code out of PLCOpenEditor controller, and compile it with IEC2C
       
   432         @param buildpath: path where files should be created
       
   433         """
       
   434 
       
   435         # Update PLCOpenEditor ConfNode Block types before generate ST code
       
   436         self.RefreshConfNodesBlockLists()
       
   437         
       
   438         self.logger.write(_("Generating SoftPLC IEC-61131 ST/IL/SFC code...\n"))
       
   439         buildpath = self._getBuildPath()
       
   440         # ask PLCOpenEditor controller to write ST/IL/SFC code file
       
   441         program, errors, warnings = self.GenerateProgram(self._getIECgeneratedcodepath())
       
   442         if len(warnings) > 0:
       
   443             self.logger.write_warning(_("Warnings in ST/IL/SFC code generator :\n"))
       
   444             for warning in warnings:
       
   445                 self.logger.write_warning("%s\n"%warning)
       
   446         if len(errors) > 0:
       
   447             # Failed !
       
   448             self.logger.write_error(_("Error in ST/IL/SFC code generator :\n%s\n")%errors[0])
       
   449             return False
       
   450         plc_file = open(self._getIECcodepath(), "w")
       
   451         # Add ST Library from confnodes
       
   452         plc_file.write(self.ConfNodesSTLibraryFactory())
       
   453         if os.path.isfile(self._getIECrawcodepath()):
       
   454             plc_file.write(open(self._getIECrawcodepath(), "r").read())
       
   455             plc_file.write("\n")
       
   456         plc_file.close()
       
   457         plc_file = open(self._getIECcodepath(), "r")
       
   458         self.ProgramOffset = 0
       
   459         for line in plc_file.xreadlines():
       
   460             self.ProgramOffset += 1
       
   461         plc_file.close()
       
   462         plc_file = open(self._getIECcodepath(), "a")
       
   463         plc_file.write(open(self._getIECgeneratedcodepath(), "r").read())
       
   464         plc_file.close()
       
   465 
       
   466         self.logger.write(_("Compiling IEC Program into C code...\n"))
       
   467 
       
   468         # Now compile IEC code into many C files
       
   469         # files are listed to stdout, and errors to stderr. 
       
   470         status, result, err_result = ProcessLogger(
       
   471                self.logger,
       
   472                "\"%s\" -f -I \"%s\" -T \"%s\" \"%s\""%(
       
   473                          self.iec2c_path,
       
   474                          self.ieclib_path, 
       
   475                          buildpath,
       
   476                          self._getIECcodepath()),
       
   477                no_stdout=True, no_stderr=True).spin()
       
   478         if status:
       
   479             # Failed !
       
   480             
       
   481             # parse iec2c's error message. if it contains a line number,
       
   482             # then print those lines from the generated IEC file.
       
   483             for err_line in err_result.split('\n'):
       
   484                 self.logger.write_warning(err_line + "\n")
       
   485 
       
   486                 m_result = MATIEC_ERROR_MODEL.match(err_line)
       
   487                 if m_result is not None:
       
   488                     first_line, first_column, last_line, last_column, error = m_result.groups()
       
   489                     first_line, last_line = int(first_line), int(last_line)
       
   490                     
       
   491                     last_section = None
       
   492                     f = open(self._getIECcodepath())
       
   493 
       
   494                     for i, line in enumerate(f.readlines()):
       
   495                         i = i + 1
       
   496                         if line[0] not in '\t \r\n':
       
   497                             last_section = line
       
   498 
       
   499                         if first_line <= i <= last_line:
       
   500                             if last_section is not None:
       
   501                                 self.logger.write_warning("In section: " + last_section)
       
   502                                 last_section = None # only write section once
       
   503                             self.logger.write_warning("%04d: %s" % (i, line))
       
   504 
       
   505                     f.close()
       
   506             
       
   507             self.logger.write_error(_("Error : IEC to C compiler returned %d\n")%status)
       
   508             return False
       
   509         
       
   510         # Now extract C files of stdout
       
   511         C_files = [ fname for fname in result.splitlines() if fname[-2:]==".c" or fname[-2:]==".C" ]
       
   512         # remove those that are not to be compiled because included by others
       
   513         C_files.remove("POUS.c")
       
   514         if not C_files:
       
   515             self.logger.write_error(_("Error : At least one configuration and one resource must be declared in PLC !\n"))
       
   516             return False
       
   517         # transform those base names to full names with path
       
   518         C_files = map(lambda filename:os.path.join(buildpath, filename), C_files)
       
   519         self.logger.write(_("Extracting Located Variables...\n"))
       
   520         # Keep track of generated located variables for later use by self._Generate_C
       
   521         self.PLCGeneratedLocatedVars = self.GetLocations()
       
   522         # Keep track of generated C files for later use by self.CTNGenerate_C
       
   523         self.PLCGeneratedCFiles = C_files
       
   524         # compute CFLAGS for plc
       
   525         self.plcCFLAGS = "\"-I"+self.ieclib_path+"\""
       
   526         return True
       
   527 
       
   528     def GetBuilder(self):
       
   529         """
       
   530         Return a Builder (compile C code into machine code)
       
   531         """
       
   532         # Get target, module and class name
       
   533         targetname = self.GetTarget().getcontent()["name"]
       
   534         modulename = "targets." + targetname
       
   535         classname = targetname + "_target"
       
   536 
       
   537         # Get module reference
       
   538         try :
       
   539             targetmodule = getattr(__import__(modulename), targetname)
       
   540 
       
   541         except Exception, msg:
       
   542             self.logger.write_error(_("Can't find module for target %s!\n")%targetname)
       
   543             self.logger.write_error(str(msg))
       
   544             return None
       
   545         
       
   546         # Get target class
       
   547         targetclass = getattr(targetmodule, classname)
       
   548 
       
   549         # if target already 
       
   550         if self._builder is None or not isinstance(self._builder,targetclass):
       
   551             # Get classname instance
       
   552             self._builder = targetclass(self)
       
   553         return self._builder
       
   554 
       
   555     def ResetBuildMD5(self):
       
   556         builder=self.GetBuilder()
       
   557         if builder is not None:
       
   558             builder.ResetBinaryCodeMD5()
       
   559         self.EnableMethod("_Transfer", False)
       
   560 
       
   561     def GetLastBuildMD5(self):
       
   562         builder=self.GetBuilder()
       
   563         if builder is not None:
       
   564             return builder.GetBinaryCodeMD5()
       
   565         else:
       
   566             return None
       
   567 
       
   568     #######################################################################
       
   569     #
       
   570     #                C CODE GENERATION METHODS
       
   571     #
       
   572     #######################################################################
       
   573     
       
   574     def CTNGenerate_C(self, buildpath, locations):
       
   575         """
       
   576         Return C code generated by iec2c compiler 
       
   577         when _generate_softPLC have been called
       
   578         @param locations: ignored
       
   579         @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND
       
   580         """
       
   581 
       
   582         return ([(C_file_name, self.plcCFLAGS) 
       
   583                 for C_file_name in self.PLCGeneratedCFiles ], 
       
   584                "", # no ldflags
       
   585                False) # do not expose retreive/publish calls
       
   586     
       
   587     def ResetIECProgramsAndVariables(self):
       
   588         """
       
   589         Reset variable and program list that are parsed from
       
   590         CSV file generated by IEC2C compiler.
       
   591         """
       
   592         self._ProgramList = None
       
   593         self._VariablesList = None
       
   594         self._IECPathToIdx = {}
       
   595         self._Ticktime = 0
       
   596         self.TracedIECPath = []
       
   597 
       
   598     def GetIECProgramsAndVariables(self):
       
   599         """
       
   600         Parse CSV-like file  VARIABLES.csv resulting from IEC2C compiler.
       
   601         Each section is marked with a line staring with '//'
       
   602         list of all variables used in various POUs
       
   603         """
       
   604         if self._ProgramList is None or self._VariablesList is None:
       
   605             try:
       
   606                 csvfile = os.path.join(self._getBuildPath(),"VARIABLES.csv")
       
   607                 # describes CSV columns
       
   608                 ProgramsListAttributeName = ["num", "C_path", "type"]
       
   609                 VariablesListAttributeName = ["num", "vartype", "IEC_path", "C_path", "type"]
       
   610                 self._ProgramList = []
       
   611                 self._VariablesList = []
       
   612                 self._IECPathToIdx = {}
       
   613                 
       
   614                 # Separate sections
       
   615                 ListGroup = []
       
   616                 for line in open(csvfile,'r').xreadlines():
       
   617                     strippedline = line.strip()
       
   618                     if strippedline.startswith("//"):
       
   619                         # Start new section
       
   620                         ListGroup.append([])
       
   621                     elif len(strippedline) > 0 and len(ListGroup) > 0:
       
   622                         # append to this section
       
   623                         ListGroup[-1].append(strippedline)
       
   624         
       
   625                 # first section contains programs
       
   626                 for line in ListGroup[0]:
       
   627                     # Split and Maps each field to dictionnary entries
       
   628                     attrs = dict(zip(ProgramsListAttributeName,line.strip().split(';')))
       
   629                     # Truncate "C_path" to remove conf an ressources names
       
   630                     attrs["C_path"] = '__'.join(attrs["C_path"].split(".",2)[1:])
       
   631                     # Push this dictionnary into result.
       
   632                     self._ProgramList.append(attrs)
       
   633         
       
   634                 # second section contains all variables
       
   635                 for line in ListGroup[1]:
       
   636                     # Split and Maps each field to dictionnary entries
       
   637                     attrs = dict(zip(VariablesListAttributeName,line.strip().split(';')))
       
   638                     # Truncate "C_path" to remove conf an ressources names
       
   639                     parts = attrs["C_path"].split(".",2)
       
   640                     if len(parts) > 2:
       
   641                         attrs["C_path"] = '__'.join(parts[1:])
       
   642                     else:
       
   643                         attrs["C_path"] = '__'.join(parts)
       
   644                     # Push this dictionnary into result.
       
   645                     self._VariablesList.append(attrs)
       
   646                     # Fill in IEC<->C translation dicts
       
   647                     IEC_path=attrs["IEC_path"]
       
   648                     Idx=int(attrs["num"])
       
   649                     self._IECPathToIdx[IEC_path]=(Idx, attrs["type"])
       
   650                 
       
   651                 # third section contains ticktime
       
   652                 if len(ListGroup) > 2:
       
   653                     self._Ticktime = int(ListGroup[2][0]) 
       
   654                 
       
   655             except Exception,e:
       
   656                 self.logger.write_error(_("Cannot open/parse VARIABLES.csv!\n"))
       
   657                 self.logger.write_error(traceback.format_exc())
       
   658                 self.ResetIECProgramsAndVariables()
       
   659                 return False
       
   660 
       
   661         return True
       
   662 
       
   663     def Generate_plc_debugger(self):
       
   664         """
       
   665         Generate trace/debug code out of PLC variable list
       
   666         """
       
   667         self.GetIECProgramsAndVariables()
       
   668 
       
   669         # prepare debug code
       
   670         debug_code = targets.code("plc_debug") % {
       
   671            "buffer_size": reduce(lambda x, y: x + y, [DebugTypesSize.get(v["type"], 0) for v in self._VariablesList], 0),
       
   672            "programs_declarations":
       
   673                "\n".join(["extern %(type)s %(C_path)s;"%p for p in self._ProgramList]),
       
   674            "extern_variables_declarations":"\n".join([
       
   675               {"EXT":"extern __IEC_%(type)s_p %(C_path)s;",
       
   676                "IN":"extern __IEC_%(type)s_p %(C_path)s;",
       
   677                "MEM":"extern __IEC_%(type)s_p %(C_path)s;",
       
   678                "OUT":"extern __IEC_%(type)s_p %(C_path)s;",
       
   679                "VAR":"extern __IEC_%(type)s_t %(C_path)s;"}[v["vartype"]]%v 
       
   680                for v in self._VariablesList if v["vartype"] != "FB" and v["C_path"].find('.')<0]),
       
   681            "for_each_variable_do_code":"\n".join([
       
   682                {"EXT":"    (*fp)((void*)&%(C_path)s,%(type)s_P_ENUM);\n",
       
   683                 "IN":"    (*fp)((void*)&%(C_path)s,%(type)s_P_ENUM);\n",
       
   684                 "MEM":"    (*fp)((void*)&%(C_path)s,%(type)s_O_ENUM);\n",
       
   685                 "OUT":"    (*fp)((void*)&%(C_path)s,%(type)s_O_ENUM);\n",
       
   686                 "VAR":"    (*fp)((void*)&%(C_path)s,%(type)s_ENUM);\n"}[v["vartype"]]%v
       
   687                 for v in self._VariablesList if v["vartype"] != "FB" and v["type"] in DebugTypesSize ]),
       
   688            "find_variable_case_code":"\n".join([
       
   689                "    case %(num)s:\n"%v+
       
   690                "        *varp = (void*)&%(C_path)s;\n"%v+
       
   691                {"EXT":"        return %(type)s_P_ENUM;\n",
       
   692                 "IN":"        return %(type)s_P_ENUM;\n",
       
   693                 "MEM":"        return %(type)s_O_ENUM;\n",
       
   694                 "OUT":"        return %(type)s_O_ENUM;\n",
       
   695                 "VAR":"        return %(type)s_ENUM;\n"}[v["vartype"]]%v
       
   696                 for v in self._VariablesList if v["vartype"] != "FB" and v["type"] in DebugTypesSize ])}
       
   697         
       
   698         return debug_code
       
   699         
       
   700     def Generate_plc_common_main(self):
       
   701         """
       
   702         Use confnodes layout given in LocationCFilesAndCFLAGS to
       
   703         generate glue code that dispatch calls to all confnodes
       
   704         """
       
   705         # filter location that are related to code that will be called
       
   706         # in retreive, publish, init, cleanup
       
   707         locstrs = map(lambda x:"_".join(map(str,x)),
       
   708            [loc for loc,Cfiles,DoCalls in self.LocationCFilesAndCFLAGS if loc and DoCalls])
       
   709 
       
   710         # Generate main, based on template
       
   711         if self.BeremizRoot.getEnable_ConfNodes():
       
   712             plc_main_code = targets.code("plc_common_main") % {
       
   713                 "calls_prototypes":"\n".join([(
       
   714                       "int __init_%(s)s(int argc,char **argv);\n"+
       
   715                       "void __cleanup_%(s)s(void);\n"+
       
   716                       "void __retrieve_%(s)s(void);\n"+
       
   717                       "void __publish_%(s)s(void);")%{'s':locstr} for locstr in locstrs]),
       
   718                 "retrieve_calls":"\n    ".join([
       
   719                       "__retrieve_%s();"%locstrs[i-1] for i in xrange(len(locstrs), 0, -1)]),
       
   720                 "publish_calls":"\n    ".join([ #Call publish in reverse order
       
   721                       "__publish_%s();"%locstr for locstr in locstrs]),
       
   722                 "init_calls":"\n    ".join([
       
   723                       "init_level=%d; "%(i+1)+
       
   724                       "if((res = __init_%s(argc,argv))){"%locstr +
       
   725                       #"printf(\"%s\"); "%locstr + #for debug
       
   726                       "return res;}" for i,locstr in enumerate(locstrs)]),
       
   727                 "cleanup_calls":"\n    ".join([
       
   728                       "if(init_level >= %d) "%i+
       
   729                       "__cleanup_%s();"%locstrs[i-1] for i in xrange(len(locstrs), 0, -1)])
       
   730                 }
       
   731         else:
       
   732             plc_main_code = targets.code("plc_common_main") % {
       
   733                 "calls_prototypes":"\n",
       
   734                 "retrieve_calls":"\n",
       
   735                 "publish_calls":"\n",
       
   736                 "init_calls":"\n",
       
   737                 "cleanup_calls":"\n"
       
   738                 }
       
   739         plc_main_code += targets.targetcode(self.GetTarget().getcontent()["name"])
       
   740         return plc_main_code
       
   741 
       
   742         
       
   743     def _Build(self):
       
   744         """
       
   745         Method called by user to (re)build SoftPLC and confnode tree
       
   746         """
       
   747         if self.AppFrame is not None:
       
   748             self.AppFrame.ClearErrors()
       
   749         
       
   750         buildpath = self._getBuildPath()
       
   751 
       
   752         # Eventually create build dir
       
   753         if not os.path.exists(buildpath):
       
   754             os.mkdir(buildpath)
       
   755         # There is something to clean
       
   756         self.EnableMethod("_Clean", True)
       
   757 
       
   758         self.logger.flush()
       
   759         self.logger.write(_("Start build in %s\n") % buildpath)
       
   760 
       
   761         # Generate SoftPLC IEC code
       
   762         IECGenRes = self._Generate_SoftPLC()
       
   763         self.ShowMethod("_showIECcode", True)
       
   764 
       
   765         # If IEC code gen fail, bail out.
       
   766         if not IECGenRes:
       
   767             self.logger.write_error(_("IEC-61131-3 code generation failed !\n"))
       
   768             self.ResetBuildMD5()
       
   769             return False
       
   770 
       
   771         # Reset variable and program list that are parsed from
       
   772         # CSV file generated by IEC2C compiler.
       
   773         self.ResetIECProgramsAndVariables()
       
   774         
       
   775         # Generate C code and compilation params from confnode hierarchy
       
   776         try:
       
   777             self.LocationCFilesAndCFLAGS, self.LDFLAGS, ExtraFiles = self._Generate_C(
       
   778                 buildpath, 
       
   779                 self.PLCGeneratedLocatedVars)
       
   780         except Exception, exc:
       
   781             self.logger.write_error(_("Runtime extensions C code generation failed !\n"))
       
   782             self.logger.write_error(traceback.format_exc())
       
   783             self.ResetBuildMD5()
       
   784             return False
       
   785 
       
   786         # Get temporary directory path
       
   787         extrafilespath = self._getExtraFilesPath()
       
   788         # Remove old directory
       
   789         if os.path.exists(extrafilespath):
       
   790             shutil.rmtree(extrafilespath)
       
   791         # Recreate directory
       
   792         os.mkdir(extrafilespath)
       
   793         # Then write the files
       
   794         for fname,fobject in ExtraFiles:
       
   795             fpath = os.path.join(extrafilespath,fname)
       
   796             open(fpath, "wb").write(fobject.read())
       
   797         # Now we can forget ExtraFiles (will close files object)
       
   798         del ExtraFiles
       
   799         
       
   800         # Template based part of C code generation
       
   801         # files are stacked at the beginning, as files of confnode tree root
       
   802         for generator, filename, name in [
       
   803            # debugger code
       
   804            (self.Generate_plc_debugger, "plc_debugger.c", "Debugger"),
       
   805            # init/cleanup/retrieve/publish, run and align code
       
   806            (self.Generate_plc_common_main,"plc_common_main.c","Common runtime")]:
       
   807             try:
       
   808                 # Do generate
       
   809                 code = generator()
       
   810                 if code is None:
       
   811                      raise
       
   812                 code_path = os.path.join(buildpath,filename)
       
   813                 open(code_path, "w").write(code)
       
   814                 # Insert this file as first file to be compiled at root confnode
       
   815                 self.LocationCFilesAndCFLAGS[0][1].insert(0,(code_path, self.plcCFLAGS))
       
   816             except Exception, exc:
       
   817                 self.logger.write_error(name+_(" generation failed !\n"))
       
   818                 self.logger.write_error(traceback.format_exc())
       
   819                 self.ResetBuildMD5()
       
   820                 return False
       
   821 
       
   822         self.logger.write(_("C code generated successfully.\n"))
       
   823 
       
   824         # Get current or fresh builder
       
   825         builder = self.GetBuilder()
       
   826         if builder is None:
       
   827             self.logger.write_error(_("Fatal : cannot get builder.\n"))
       
   828             self.ResetBuildMD5()
       
   829             return False
       
   830 
       
   831         # Build
       
   832         try:
       
   833             if not builder.build() :
       
   834                 self.logger.write_error(_("C Build failed.\n"))
       
   835                 return False
       
   836         except Exception, exc:
       
   837             self.logger.write_error(_("C Build crashed !\n"))
       
   838             self.logger.write_error(traceback.format_exc())
       
   839             self.ResetBuildMD5()
       
   840             return False
       
   841 
       
   842         self.logger.write(_("Successfully built.\n"))
       
   843         # Update GUI status about need for transfer
       
   844         self.CompareLocalAndRemotePLC()
       
   845         return True
       
   846     
       
   847     def ShowError(self, logger, from_location, to_location):
       
   848         chunk_infos = self.GetChunkInfos(from_location, to_location)
       
   849         for infos, (start_row, start_col) in chunk_infos:
       
   850             start = (from_location[0] - start_row, from_location[1] - start_col)
       
   851             end = (to_location[0] - start_row, to_location[1] - start_col)
       
   852             #print from_location, to_location, start_row, start_col, start, end
       
   853             if self.AppFrame is not None:
       
   854                 self.AppFrame.ShowError(infos, start, end)
       
   855 
       
   856     def _showIECcode(self):
       
   857         self._OpenView("IEC code")
       
   858 
       
   859     def _editIECrawcode(self):
       
   860         self._OpenView("IEC raw code")
       
   861 
       
   862     def _OpenView(self, name=None):
       
   863         if name == "IEC code":
       
   864             plc_file = self._getIECcodepath()
       
   865         
       
   866             IEC_code_viewer = TextViewer(self.AppFrame.TabsOpened, "", None, None, instancepath=name)
       
   867             #IEC_code_viewer.Enable(False)
       
   868             IEC_code_viewer.SetTextSyntax("ALL")
       
   869             IEC_code_viewer.SetKeywords(IEC_KEYWORDS)
       
   870             try:
       
   871                 text = file(plc_file).read()
       
   872             except:
       
   873                 text = '(* No IEC code have been generated at that time ! *)'
       
   874             IEC_code_viewer.SetText(text = text)
       
   875             IEC_code_viewer.SetIcon(self.AppFrame.GenerateBitmap("ST"))
       
   876                 
       
   877             self.AppFrame.EditProjectElement(IEC_code_viewer, name)
       
   878             
       
   879             return IEC_code_viewer
       
   880         
       
   881         elif name == "IEC raw code":
       
   882             controler = MiniTextControler(self._getIECrawcodepath())
       
   883             IEC_raw_code_viewer = TextViewer(self.AppFrame.TabsOpened, "", None, controler, instancepath=name)
       
   884             #IEC_raw_code_viewer.Enable(False)
       
   885             IEC_raw_code_viewer.SetTextSyntax("ALL")
       
   886             IEC_raw_code_viewer.SetKeywords(IEC_KEYWORDS)
       
   887             IEC_raw_code_viewer.RefreshView()
       
   888             IEC_raw_code_viewer.SetIcon(self.AppFrame.GenerateBitmap("ST"))
       
   889                 
       
   890             self.AppFrame.EditProjectElement(IEC_raw_code_viewer, name)
       
   891 
       
   892             return IEC_raw_code_viewer
       
   893         
       
   894         return None
       
   895 
       
   896     def _Clean(self):
       
   897         if os.path.isdir(os.path.join(self._getBuildPath())):
       
   898             self.logger.write(_("Cleaning the build directory\n"))
       
   899             shutil.rmtree(os.path.join(self._getBuildPath()))
       
   900         else:
       
   901             self.logger.write_error(_("Build directory already clean\n"))
       
   902         self.ShowMethod("_showIECcode", False)
       
   903         self.EnableMethod("_Clean", False)
       
   904         # kill the builder
       
   905         self._builder = None
       
   906         self.CompareLocalAndRemotePLC()
       
   907 
       
   908     ############# Real PLC object access #############
       
   909     def UpdateMethodsFromPLCStatus(self):
       
   910         # Get PLC state : Running or Stopped
       
   911         # TODO : use explicit status instead of boolean
       
   912         status = None
       
   913         if self._connector is not None:
       
   914             status = self._connector.GetPLCstatus()
       
   915         if status is None:
       
   916             self._connector = None
       
   917             status = "Disconnected"
       
   918         if(self.previous_plcstate != status):
       
   919             for args in {
       
   920                      "Started" :     [("_Run", False),
       
   921                                       ("_Stop", True)],
       
   922                      "Stopped" :     [("_Run", True),
       
   923                                       ("_Stop", False)],
       
   924                      "Empty" :       [("_Run", False),
       
   925                                       ("_Stop", False)],
       
   926                      "Broken" :      [],
       
   927                      "Disconnected" :[("_Run", False),
       
   928                                       ("_Stop", False),
       
   929                                       ("_Transfer", False),
       
   930                                       ("_Connect", True),
       
   931                                       ("_Disconnect", False)],
       
   932                    }.get(status,[]):
       
   933                 self.ShowMethod(*args)
       
   934             self.previous_plcstate = status
       
   935             return True
       
   936         return False
       
   937     
       
   938     def PullPLCStatusProc(self, event):
       
   939         if self._connector is None:
       
   940             self.StatusTimer.Stop()
       
   941         if self.UpdateMethodsFromPLCStatus():
       
   942             
       
   943             status = _(self.previous_plcstate)
       
   944             {"Broken": self.logger.write_error,
       
   945              None: lambda x: None}.get(
       
   946                 self.previous_plcstate, self.logger.write)(_("PLC is %s\n")%status)
       
   947             self.AppFrame.RefreshAll()
       
   948         
       
   949     def RegisterDebugVarToConnector(self):
       
   950         self.DebugTimer=None
       
   951         Idxs = []
       
   952         self.TracedIECPath = []
       
   953         if self._connector is not None:
       
   954             self.IECdebug_lock.acquire()
       
   955             IECPathsToPop = []
       
   956             for IECPath,data_tuple in self.IECdebug_datas.iteritems():
       
   957                 WeakCallableDict, data_log, status, fvalue = data_tuple
       
   958                 if len(WeakCallableDict) == 0:
       
   959                     # Callable Dict is empty.
       
   960                     # This variable is not needed anymore!
       
   961                     #print "Unused : " + IECPath
       
   962                     IECPathsToPop.append(IECPath)
       
   963                 elif IECPath != "__tick__":
       
   964                     # Convert 
       
   965                     Idx, IEC_Type = self._IECPathToIdx.get(IECPath,(None,None))
       
   966                     if Idx is not None:
       
   967                         if IEC_Type in DebugTypesSize: 
       
   968                             Idxs.append((Idx, IEC_Type, fvalue, IECPath))
       
   969                         else:
       
   970                             self.logger.write_warning(_("Debug : Unsuppoted type to debug %s\n")%IEC_Type)
       
   971                     else:
       
   972                         self.logger.write_warning(_("Debug : Unknown variable %s\n")%IECPath)
       
   973             for IECPathToPop in IECPathsToPop:
       
   974                 self.IECdebug_datas.pop(IECPathToPop)
       
   975 
       
   976             if Idxs:
       
   977                 Idxs.sort()
       
   978                 self.TracedIECPath = zip(*Idxs)[3]
       
   979                 self._connector.SetTraceVariablesList(zip(*zip(*Idxs)[0:3]))
       
   980             else:
       
   981                 self.TracedIECPath = []
       
   982                 self._connector.SetTraceVariablesList([])
       
   983             self.IECdebug_lock.release()
       
   984             
       
   985             #for IEC_path, IECdebug_data in self.IECdebug_datas.iteritems():
       
   986             #    print IEC_path, IECdebug_data[0].keys()
       
   987 
       
   988     def ReArmDebugRegisterTimer(self):
       
   989         if self.DebugTimer is not None:
       
   990             self.DebugTimer.cancel()
       
   991 
       
   992         # Timer to prevent rapid-fire when registering many variables
       
   993         # use wx.CallAfter use keep using same thread. TODO : use wx.Timer instead
       
   994         self.DebugTimer=Timer(0.5,wx.CallAfter,args = [self.RegisterDebugVarToConnector])
       
   995         # Rearm anti-rapid-fire timer
       
   996         self.DebugTimer.start()
       
   997 
       
   998     def GetDebugIECVariableType(self, IECPath):
       
   999         Idx, IEC_Type = self._IECPathToIdx.get(IECPath,(None,None))
       
  1000         return IEC_Type
       
  1001         
       
  1002     def SubscribeDebugIECVariable(self, IECPath, callableobj, *args, **kwargs):
       
  1003         """
       
  1004         Dispatching use a dictionnary linking IEC variable paths
       
  1005         to a WeakKeyDictionary linking 
       
  1006         weakly referenced callables to optionnal args
       
  1007         """
       
  1008         if IECPath != "__tick__" and not self._IECPathToIdx.has_key(IECPath):
       
  1009             return None
       
  1010         
       
  1011         self.IECdebug_lock.acquire()
       
  1012         # If no entry exist, create a new one with a fresh WeakKeyDictionary
       
  1013         IECdebug_data = self.IECdebug_datas.get(IECPath, None)
       
  1014         if IECdebug_data is None:
       
  1015             IECdebug_data  = [
       
  1016                     WeakKeyDictionary(), # Callables
       
  1017                     [],                  # Data storage [(tick, data),...]
       
  1018                     "Registered",        # Variable status
       
  1019                     None]                # Forced value
       
  1020             self.IECdebug_datas[IECPath] = IECdebug_data
       
  1021         
       
  1022         IECdebug_data[0][callableobj]=(args, kwargs)
       
  1023 
       
  1024         self.IECdebug_lock.release()
       
  1025         
       
  1026         self.ReArmDebugRegisterTimer()
       
  1027         
       
  1028         return IECdebug_data[1]
       
  1029 
       
  1030     def UnsubscribeDebugIECVariable(self, IECPath, callableobj):
       
  1031         #print "Unsubscribe", IECPath, callableobj
       
  1032         self.IECdebug_lock.acquire()
       
  1033         IECdebug_data = self.IECdebug_datas.get(IECPath, None)
       
  1034         if IECdebug_data is not None:
       
  1035             IECdebug_data[0].pop(callableobj,None)
       
  1036         self.IECdebug_lock.release()
       
  1037 
       
  1038         self.ReArmDebugRegisterTimer()
       
  1039 
       
  1040     def UnsubscribeAllDebugIECVariable(self):
       
  1041         self.IECdebug_lock.acquire()
       
  1042         IECdebug_data = {}
       
  1043         self.IECdebug_lock.release()
       
  1044 
       
  1045         self.ReArmDebugRegisterTimer()
       
  1046 
       
  1047     def ForceDebugIECVariable(self, IECPath, fvalue):
       
  1048         if not self.IECdebug_datas.has_key(IECPath):
       
  1049             return
       
  1050         
       
  1051         self.IECdebug_lock.acquire()
       
  1052         
       
  1053         # If no entry exist, create a new one with a fresh WeakKeyDictionary
       
  1054         IECdebug_data = self.IECdebug_datas.get(IECPath, None)
       
  1055         IECdebug_data[2] = "Forced"
       
  1056         IECdebug_data[3] = fvalue
       
  1057         
       
  1058         self.IECdebug_lock.release()
       
  1059         
       
  1060         self.ReArmDebugRegisterTimer()
       
  1061     
       
  1062     def ReleaseDebugIECVariable(self, IECPath):
       
  1063         if not self.IECdebug_datas.has_key(IECPath):
       
  1064             return
       
  1065         
       
  1066         self.IECdebug_lock.acquire()
       
  1067         
       
  1068         # If no entry exist, create a new one with a fresh WeakKeyDictionary
       
  1069         IECdebug_data = self.IECdebug_datas.get(IECPath, None)
       
  1070         IECdebug_data[2] = "Registered"
       
  1071         IECdebug_data[3] = None
       
  1072         
       
  1073         self.IECdebug_lock.release()
       
  1074         
       
  1075         self.ReArmDebugRegisterTimer()
       
  1076     
       
  1077     def CallWeakcallables(self, IECPath, function_name, *cargs):
       
  1078         data_tuple = self.IECdebug_datas.get(IECPath, None)
       
  1079         if data_tuple is not None:
       
  1080             WeakCallableDict, data_log, status, fvalue = data_tuple
       
  1081             #data_log.append((debug_tick, value))
       
  1082             for weakcallable,(args,kwargs) in WeakCallableDict.iteritems():
       
  1083                 #print weakcallable, value, args, kwargs
       
  1084                 function = getattr(weakcallable, function_name, None)
       
  1085                 if function is not None:
       
  1086                     if status == "Forced" and cargs[1] == fvalue:
       
  1087                         function(*(cargs + (True,) + args), **kwargs)
       
  1088                     else:
       
  1089                         function(*(cargs + args), **kwargs)
       
  1090                 # This will block thread if more than one call is waiting
       
  1091 
       
  1092     def GetTicktime(self):
       
  1093         return self._Ticktime
       
  1094 
       
  1095     def RemoteExec(self, script, **kwargs):
       
  1096         if self._connector is None:
       
  1097             return -1, "No runtime connected!"
       
  1098         return self._connector.RemoteExec(script, **kwargs)
       
  1099 
       
  1100     def DebugThreadProc(self):
       
  1101         """
       
  1102         This thread waid PLC debug data, and dispatch them to subscribers
       
  1103         """
       
  1104         self.debug_break = False
       
  1105         debug_getvar_retry = 0
       
  1106         while (not self.debug_break) and (self._connector is not None):
       
  1107             Trace = self._connector.GetTraceVariables()
       
  1108             if(Trace):
       
  1109                 plc_status, debug_tick, debug_vars = Trace
       
  1110             else:
       
  1111                 plc_status = None
       
  1112             debug_getvar_retry += 1
       
  1113             #print debug_tick, debug_vars
       
  1114             if plc_status == "Started":
       
  1115                 self.IECdebug_lock.acquire()
       
  1116                 if len(debug_vars) == len(self.TracedIECPath):
       
  1117                     if debug_getvar_retry > DEBUG_RETRIES_WARN:
       
  1118                         self.logger.write(_("... debugger recovered\n"))
       
  1119                     debug_getvar_retry = 0
       
  1120                     for IECPath,value in zip(self.TracedIECPath, debug_vars):
       
  1121                         if value is not None:
       
  1122                             self.CallWeakcallables(IECPath, "NewValue", debug_tick, value)
       
  1123                     self.CallWeakcallables("__tick__", "NewDataAvailable")
       
  1124                 self.IECdebug_lock.release()
       
  1125                 if debug_getvar_retry == DEBUG_RETRIES_WARN:
       
  1126                     self.logger.write(_("Waiting debugger to recover...\n"))
       
  1127                 if debug_getvar_retry == DEBUG_RETRIES_REREGISTER:
       
  1128                     # re-register debug registry to PLC
       
  1129                     wx.CallAfter(self.RegisterDebugVarToConnector)
       
  1130                 if debug_getvar_retry != 0:
       
  1131                     # Be patient, tollerate PLC to come up before debugging
       
  1132                     time.sleep(0.1)
       
  1133             else:
       
  1134                 self.debug_break = True
       
  1135         self.logger.write(_("Debugger disabled\n"))
       
  1136         self.DebugThread = None
       
  1137 
       
  1138     def KillDebugThread(self):
       
  1139         tmp_debugthread = self.DebugThread
       
  1140         self.debug_break = True
       
  1141         if tmp_debugthread is not None:
       
  1142             self.logger.writeyield(_("Stopping debugger...\n"))
       
  1143             tmp_debugthread.join(timeout=5)
       
  1144             if tmp_debugthread.isAlive() and self.logger:
       
  1145                 self.logger.write_warning(_("Couldn't stop debugger.\n"))
       
  1146             else:
       
  1147                 self.logger.write(_("Debugger stopped.\n"))
       
  1148         self.DebugThread = None
       
  1149 
       
  1150     def _connect_debug(self): 
       
  1151         if self.AppFrame:
       
  1152             self.AppFrame.ResetGraphicViewers()
       
  1153         self.RegisterDebugVarToConnector()
       
  1154         if self.DebugThread is None:
       
  1155             self.DebugThread = Thread(target=self.DebugThreadProc)
       
  1156             self.DebugThread.start()
       
  1157     
       
  1158     def _Run(self):
       
  1159         """
       
  1160         Start PLC
       
  1161         """
       
  1162         if self.GetIECProgramsAndVariables():
       
  1163             self._connector.StartPLC()
       
  1164             self.logger.write(_("Starting PLC\n"))
       
  1165             self._connect_debug()
       
  1166         else:
       
  1167             self.logger.write_error(_("Couldn't start PLC !\n"))
       
  1168         wx.CallAfter(self.UpdateMethodsFromPLCStatus)
       
  1169        
       
  1170     def _Stop(self):
       
  1171         """
       
  1172         Stop PLC
       
  1173         """
       
  1174         if self._connector is not None and not self._connector.StopPLC():
       
  1175             self.logger.write_error(_("Couldn't stop PLC !\n"))
       
  1176 
       
  1177         # debugthread should die on his own
       
  1178         #self.KillDebugThread()
       
  1179         
       
  1180         wx.CallAfter(self.UpdateMethodsFromPLCStatus)
       
  1181 
       
  1182     def _Connect(self):
       
  1183         # don't accept re-connetion is already connected
       
  1184         if self._connector is not None:
       
  1185             self.logger.write_error(_("Already connected. Please disconnect\n"))
       
  1186             return
       
  1187         
       
  1188         # Get connector uri
       
  1189         uri = self.\
       
  1190               BeremizRoot.\
       
  1191               getURI_location().\
       
  1192               strip()
       
  1193 
       
  1194         # if uri is empty launch discovery dialog
       
  1195         if uri == "":
       
  1196             # Launch Service Discovery dialog
       
  1197             dialog = DiscoveryDialog(self.AppFrame)
       
  1198             answer = dialog.ShowModal()
       
  1199             uri = dialog.GetURI()
       
  1200             dialog.Destroy()
       
  1201             
       
  1202             # Nothing choosed or cancel button
       
  1203             if uri is None or answer == wx.ID_CANCEL:
       
  1204                 self.logger.write_error(_("Connection canceled!\n"))
       
  1205                 return
       
  1206             else:
       
  1207                 self.\
       
  1208                 BeremizRoot.\
       
  1209                 setURI_location(uri)
       
  1210        
       
  1211         # Get connector from uri
       
  1212         try:
       
  1213             self._connector = connectors.ConnectorFactory(uri, self)
       
  1214         except Exception, msg:
       
  1215             self.logger.write_error(_("Exception while connecting %s!\n")%uri)
       
  1216             self.logger.write_error(traceback.format_exc())
       
  1217 
       
  1218         # Did connection success ?
       
  1219         if self._connector is None:
       
  1220             # Oups.
       
  1221             self.logger.write_error(_("Connection failed to %s!\n")%uri)
       
  1222         else:
       
  1223             self.ShowMethod("_Connect", False)
       
  1224             self.ShowMethod("_Disconnect", True)
       
  1225             self.ShowMethod("_Transfer", True)
       
  1226 
       
  1227             self.CompareLocalAndRemotePLC()
       
  1228             
       
  1229             # Init with actual PLC status and print it
       
  1230             self.UpdateMethodsFromPLCStatus()
       
  1231             if self.previous_plcstate is not None:
       
  1232                 status = _(self.previous_plcstate)
       
  1233             else:
       
  1234                 status = ""
       
  1235             self.logger.write(_("PLC is %s\n")%status)
       
  1236             
       
  1237             # Start the status Timer
       
  1238             self.StatusTimer.Start(milliseconds=500, oneShot=False)
       
  1239             
       
  1240             if self.previous_plcstate=="Started":
       
  1241                 if self.DebugAvailable() and self.GetIECProgramsAndVariables():
       
  1242                     self.logger.write(_("Debug connect matching running PLC\n"))
       
  1243                     self._connect_debug()
       
  1244                 else:
       
  1245                     self.logger.write_warning(_("Debug do not match PLC - stop/transfert/start to re-enable\n"))
       
  1246 
       
  1247     def CompareLocalAndRemotePLC(self):
       
  1248         if self._connector is None:
       
  1249             return
       
  1250         # We are now connected. Update button status
       
  1251         MD5 = self.GetLastBuildMD5()
       
  1252         # Check remote target PLC correspondance to that md5
       
  1253         if MD5 is not None:
       
  1254             if not self._connector.MatchMD5(MD5):
       
  1255 #                self.logger.write_warning(
       
  1256 #                   _("Latest build does not match with target, please transfer.\n"))
       
  1257                 self.EnableMethod("_Transfer", True)
       
  1258             else:
       
  1259 #                self.logger.write(
       
  1260 #                   _("Latest build matches target, no transfer needed.\n"))
       
  1261                 self.EnableMethod("_Transfer", True)
       
  1262                 # warns controller that program match
       
  1263                 self.ProgramTransferred()
       
  1264                 #self.EnableMethod("_Transfer", False)
       
  1265         else:
       
  1266 #            self.logger.write_warning(
       
  1267 #                _("Cannot compare latest build to target. Please build.\n"))
       
  1268             self.EnableMethod("_Transfer", False)
       
  1269 
       
  1270 
       
  1271     def _Disconnect(self):
       
  1272         self._connector = None
       
  1273         self.StatusTimer.Stop()
       
  1274         wx.CallAfter(self.UpdateMethodsFromPLCStatus)
       
  1275         
       
  1276     def _Transfer(self):
       
  1277         # Get the last build PLC's 
       
  1278         MD5 = self.GetLastBuildMD5()
       
  1279         
       
  1280         # Check if md5 file is empty : ask user to build PLC 
       
  1281         if MD5 is None :
       
  1282             self.logger.write_error(_("Failed : Must build before transfer.\n"))
       
  1283             return False
       
  1284 
       
  1285         # Compare PLC project with PLC on target
       
  1286         if self._connector.MatchMD5(MD5):
       
  1287             self.logger.write(
       
  1288                 _("Latest build already matches current target. Transfering anyway...\n"))
       
  1289 
       
  1290         # Get temprary directory path
       
  1291         extrafilespath = self._getExtraFilesPath()
       
  1292         extrafiles = [(name, open(os.path.join(extrafilespath, name), 
       
  1293                                   'rb').read()) \
       
  1294                       for name in os.listdir(extrafilespath) \
       
  1295                       if not name=="CVS"]
       
  1296 
       
  1297         # Send PLC on target
       
  1298         builder = self.GetBuilder()
       
  1299         if builder is not None:
       
  1300             data = builder.GetBinaryCode()
       
  1301             if data is not None :
       
  1302                 if self._connector.NewPLC(MD5, data, extrafiles) and self.GetIECProgramsAndVariables():
       
  1303                     self.UnsubscribeAllDebugIECVariable()
       
  1304                     self.ProgramTransferred()
       
  1305                     if self.AppFrame is not None:
       
  1306                         self.AppFrame.RefreshInstancesTree()
       
  1307                         self.AppFrame.CloseObsoleteDebugTabs()
       
  1308                     self.logger.write(_("Transfer completed successfully.\n"))
       
  1309                 else:
       
  1310                     self.logger.write_error(_("Transfer failed\n"))
       
  1311             else:
       
  1312                 self.logger.write_error(_("No PLC to transfer (did build succeed ?)\n"))
       
  1313 
       
  1314         wx.CallAfter(self.UpdateMethodsFromPLCStatus)
       
  1315 
       
  1316     ConfNodeMethods = [
       
  1317         {"bitmap" : opjimg("Build"),
       
  1318          "name" : _("Build"),
       
  1319          "tooltip" : _("Build project into build folder"),
       
  1320          "method" : "_Build"},
       
  1321         {"bitmap" : opjimg("Clean"),
       
  1322          "name" : _("Clean"),
       
  1323          "enabled" : False,
       
  1324          "tooltip" : _("Clean project build folder"),
       
  1325          "method" : "_Clean"},
       
  1326         {"bitmap" : opjimg("Run"),
       
  1327          "name" : _("Run"),
       
  1328          "shown" : False,
       
  1329          "tooltip" : _("Start PLC"),
       
  1330          "method" : "_Run"},
       
  1331         {"bitmap" : opjimg("Stop"),
       
  1332          "name" : _("Stop"),
       
  1333          "shown" : False,
       
  1334          "tooltip" : _("Stop Running PLC"),
       
  1335          "method" : "_Stop"},
       
  1336         {"bitmap" : opjimg("Connect"),
       
  1337          "name" : _("Connect"),
       
  1338          "tooltip" : _("Connect to the target PLC"),
       
  1339          "method" : "_Connect"},
       
  1340         {"bitmap" : opjimg("Transfer"),
       
  1341          "name" : _("Transfer"),
       
  1342          "shown" : False,
       
  1343          "tooltip" : _("Transfer PLC"),
       
  1344          "method" : "_Transfer"},
       
  1345         {"bitmap" : opjimg("Disconnect"),
       
  1346          "name" : _("Disconnect"),
       
  1347          "shown" : False,
       
  1348          "tooltip" : _("Disconnect from PLC"),
       
  1349          "method" : "_Disconnect"},
       
  1350         {"bitmap" : opjimg("ShowIECcode"),
       
  1351          "name" : _("Show code"),
       
  1352          "shown" : False,
       
  1353          "tooltip" : _("Show IEC code generated by PLCGenerator"),
       
  1354          "method" : "_showIECcode"},
       
  1355         {"bitmap" : opjimg("editIECrawcode"),
       
  1356          "name" : _("Raw IEC code"),
       
  1357          "tooltip" : _("Edit raw IEC code added to code generated by PLCGenerator"),
       
  1358          "method" : "_editIECrawcode"},
       
  1359     ]