ProjectController.py
branch1.1 Korean release
changeset 968 eee7625de1f7
parent 923 6ef6e0b3a908
child 978 3290eff761f1
--- a/ProjectController.py	Wed Aug 29 21:14:23 2012 +0200
+++ b/ProjectController.py	Thu Mar 07 11:47:43 2013 +0900
@@ -14,22 +14,23 @@
 
 import targets
 import connectors
-from util.misc import CheckPathPerm, GetClassImporter, IECCodeViewer
+from util.misc import CheckPathPerm, GetClassImporter
 from util.MiniTextControler import MiniTextControler
 from util.ProcessLogger import ProcessLogger
-from util.FileManagementPanel import FileManagementPanel
+from util.BitmapLibrary import GetBitmap
+from editors.FileManagementPanel import FileManagementPanel
+from editors.ProjectNodeEditor import ProjectNodeEditor
+from editors.IECCodeViewer import IECCodeViewer
+from graphics import DebugViewer
+from dialogs import DiscoveryDialog
 from PLCControler import PLCControler
-from TextViewer import TextViewer
 from plcopen.structures import IEC_KEYWORDS
-from targets.typemapping import DebugTypesSize
-from util.discovery import DiscoveryDialog
+from targets.typemapping import DebugTypesSize, LogLevelsCount, LogLevels
 from ConfigTreeNode import ConfigTreeNode
-from ProjectNodeEditor import ProjectNodeEditor
-from utils.BitmapLibrary import GetBitmap
 
 base_folder = os.path.split(sys.path[0])[0]
 
-MATIEC_ERROR_MODEL = re.compile(".*\.st:(\d+)-(\d+)\.\.(\d+)-(\d+): error : (.*)$")
+MATIEC_ERROR_MODEL = re.compile(".*\.st:(\d+)-(\d+)\.\.(\d+)-(\d+): (?:error)|(?:warning) : (.*)$")
 
 DEBUG_RETRIES_WARN = 3
 DEBUG_RETRIES_REREGISTER = 4
@@ -112,6 +113,7 @@
         self.DebugThread = None
         self.debug_break = False
         self.previous_plcstate = None
+        self.previous_log_count = [None]*LogLevelsCount
         # copy ConfNodeMethods so that it can be later customized
         self.StatusMethods = [dic.copy() for dic in self.StatusMethods]
 
@@ -237,6 +239,10 @@
             os.mkdir(projectfiles_path)
         return projectfiles_path
     
+    def AddProjectDefaultConfiguration(self, config_name="config", res_name="resource1"):
+        self.ProjectAddConfiguration(config_name)
+        self.ProjectAddConfigurationResource(config_name, res_name)
+
     def NewProject(self, ProjectPath, BuildPath=None):
         """
         Create a new project in an empty folder
@@ -254,8 +260,7 @@
              "productVersion": "1",
              "companyName": _("Unknown"),
              "creationDateTime": datetime(*localtime()[:6])})
-        self.ProjectAddConfiguration("config")
-        self.ProjectAddConfigurationResource("config", "resource1")
+        self.AddProjectDefaultConfiguration()
         
         # Change XSD into class members
         self._AddParamsMembers()
@@ -284,6 +289,8 @@
         result = self.OpenXMLFile(plc_file)
         if result:
             return result
+        if len(self.GetProjectConfigNames()) == 0:
+            self.AddProjectDefaultConfiguration()
         # Change XSD into class members
         self._AddParamsMembers()
         self.Children = {}
@@ -382,8 +389,8 @@
             res=lib.Generate_C(buildpath,self._VariablesList,LibIECCflags)  
             LocatedCCodeAndFlags.append(res[:2])
             if len(res)>2:
-                Extras.append(res[2:])
-        return map(list,zip(*LocatedCCodeAndFlags))+[tuple(*Extras)]
+                Extras.extend(res[2:])
+        return map(list,zip(*LocatedCCodeAndFlags))+[tuple(Extras)]
     
     # Update PLCOpenEditor ConfNode Block types from loaded confnodes
     def RefreshConfNodesBlockLists(self):
@@ -403,7 +410,7 @@
             self.AppFrame.RefreshPouInstanceVariablesPanel()
             self.AppFrame.RefreshFileMenu()
             self.AppFrame.RefreshEditMenu()
-            self.AppFrame.RefreshEditor()
+            wx.CallAfter(self.AppFrame.RefreshEditor)
     
     def GetVariableLocationTree(self):
         '''
@@ -498,7 +505,10 @@
                     # finally store into located variable list
                     locations.append(resdict)
         return locations
-        
+    
+    def GetConfNodeGlobalInstances(self):
+        return self._GlobalInstances()
+    
     def _Generate_SoftPLC(self):
         """
         Generate SoftPLC ST/IL/SFC code out of PLCOpenEditor controller, and compile it with IEC2C
@@ -692,15 +702,23 @@
                     self._ProgramList.append(attrs)
         
                 # second section contains all variables
+                config_FBs = {}
                 for line in ListGroup[1]:
                     # Split and Maps each field to dictionnary entries
                     attrs = dict(zip(VariablesListAttributeName,line.strip().split(';')))
                     # Truncate "C_path" to remove conf an ressources names
                     parts = attrs["C_path"].split(".",2)
                     if len(parts) > 2:
-                        attrs["C_path"] = '__'.join(parts[1:])
+                        config_FB = config_FBs.get(tuple(parts[:2]))
+                        if config_FB:
+                            parts = [config_FB] + parts[2:]
+                            attrs["C_path"] = '.'.join(parts)
+                        else: 
+                            attrs["C_path"] = '__'.join(parts[1:])
                     else:
                         attrs["C_path"] = '__'.join(parts)
+                        if attrs["vartype"] == "FB":
+                            config_FBs[tuple(parts)] = attrs["C_path"]
                     # Push this dictionnary into result.
                     self._VariablesList.append(attrs)
                     # Fill in IEC<->C translation dicts
@@ -736,18 +754,19 @@
                "IN":"extern __IEC_%(type)s_p %(C_path)s;",
                "MEM":"extern __IEC_%(type)s_p %(C_path)s;",
                "OUT":"extern __IEC_%(type)s_p %(C_path)s;",
-               "VAR":"extern __IEC_%(type)s_t %(C_path)s;"}[v["vartype"]]%v 
-               for v in self._VariablesList if v["vartype"] != "FB" and v["C_path"].find('.')<0]),
+               "VAR":"extern __IEC_%(type)s_t %(C_path)s;",
+               "FB":"extern %(type)s %(C_path)s;"}[v["vartype"]]%v 
+               for v in self._VariablesList if v["C_path"].find('.')<0]),
            "for_each_variable_do_code":"\n".join([
-               {"EXT":"    (*fp)((void*)&%(C_path)s,%(type)s_P_ENUM);\n",
-                "IN":"    (*fp)((void*)&%(C_path)s,%(type)s_P_ENUM);\n",
-                "MEM":"    (*fp)((void*)&%(C_path)s,%(type)s_O_ENUM);\n",
-                "OUT":"    (*fp)((void*)&%(C_path)s,%(type)s_O_ENUM);\n",
-                "VAR":"    (*fp)((void*)&%(C_path)s,%(type)s_ENUM);\n"}[v["vartype"]]%v
+               {"EXT":"    (*fp)((void*)&(%(C_path)s),%(type)s_P_ENUM);\n",
+                "IN":"    (*fp)((void*)&(%(C_path)s),%(type)s_P_ENUM);\n",
+                "MEM":"    (*fp)((void*)&(%(C_path)s),%(type)s_O_ENUM);\n",
+                "OUT":"    (*fp)((void*)&(%(C_path)s),%(type)s_O_ENUM);\n",
+                "VAR":"    (*fp)((void*)&(%(C_path)s),%(type)s_ENUM);\n"}[v["vartype"]]%v
                 for v in self._VariablesList if v["vartype"] != "FB" and v["type"] in DebugTypesSize ]),
            "find_variable_case_code":"\n".join([
                "    case %(num)s:\n"%v+
-               "        *varp = (void*)&%(C_path)s;\n"%v+
+               "        *varp = (void*)&(%(C_path)s);\n"%v+
                {"EXT":"        return %(type)s_P_ENUM;\n",
                 "IN":"        return %(type)s_P_ENUM;\n",
                 "MEM":"        return %(type)s_O_ENUM;\n",
@@ -856,7 +875,7 @@
         self.LocationCFilesAndCFLAGS =  CTNLocationCFilesAndCFLAGS + LibCFilesAndCFLAGS
         self.LDFLAGS = CTNLDFLAGS + LibLDFLAGS
         ExtraFiles = CTNExtraFiles + LibExtraFiles
-
+        
         # Get temporary directory path
         extrafilespath = self._getExtraFilesPath()
         # Remove old directory
@@ -923,7 +942,6 @@
         for infos, (start_row, start_col) in chunk_infos:
             start = (from_location[0] - start_row, from_location[1] - start_col)
             end = (to_location[0] - start_row, to_location[1] - start_col)
-            #print from_location, to_location, start_row, start_col, start, end
             if self.AppFrame is not None:
                 self.AppFrame.ShowError(infos, start, end)
     
@@ -937,7 +955,7 @@
     
     _ProjectFilesView = None
     def _OpenProjectFiles(self):
-        self._OpenView("Project files")
+        self._OpenView("Project Files")
     
     _FileEditors = {}
     def _OpenFileEditor(self, filepath):
@@ -980,7 +998,7 @@
             
             return self._IECRawCodeView
         
-        elif name == "Project files":
+        elif name == "Project Files":
             if self._ProjectFilesView is None:
                 self._ProjectFilesView = FileManagementPanel(self.AppFrame.TabsOpened, self, name, self._getProjectFilesPath(), True)
                 
@@ -988,7 +1006,7 @@
                 for extension, name, editor in features.file_editors:
                     if extension not in extensions:
                         extensions.append(extension)
-                self._ProjectFilesView.SetEditableFileExtensions(extensions)
+                self._ProjectFilesView.SetEditableFileExtensions(extensions) 
                 
             if self._ProjectFilesView is not None:
                 self.AppFrame.EditProjectElement(self._ProjectFilesView, name)
@@ -1023,6 +1041,8 @@
                         editor = editors[editor_name]()
                         self._FileEditors[filepath] = editor(self.AppFrame.TabsOpened, self, name, self.AppFrame)
                         self._FileEditors[filepath].SetIcon(GetBitmap("FILE"))
+                        if isinstance(self._FileEditors[filepath], DebugViewer):
+                            self._FileEditors[filepath].SetDataProducer(self)
             
             if self._FileEditors.has_key(filepath):
                 editor = self._FileEditors[filepath]
@@ -1056,13 +1076,44 @@
         self._builder = None
         self.CompareLocalAndRemotePLC()
 
-    ############# Real PLC object access #############
+    def UpdatePLCLog(self, log_count):
+        if log_count :
+            to_console = []
+            for level, count, prev in zip(xrange(LogLevelsCount), log_count,self.previous_log_count):
+                if count is not None and prev != count:
+                    # XXX replace dump to console with dedicated log panel.
+                    dump_end = max( # request message sent after the last one we already got
+                        prev - 1 if prev is not None else -1,
+                        count - 100) # 100 is purely arbitrary number
+                        # dedicated panel should only ask for a small range, 
+                        # depending on how user navigate in the panel
+                        # and only ask for last one in follow mode
+                    for msgidx in xrange(count-1, dump_end,-1):
+                        answer = self._connector.GetLogMessage(level, msgidx)
+                        if answer is not None :
+                            msg, tick, tv_sec, tv_nsec = answer 
+                            to_console.insert(0,(
+                                (tv_sec, tv_nsec),
+                                '%d|%s.%9.9d|%s(%s)'%(
+                                    int(tick),
+                                    str(datetime.fromtimestamp(tv_sec)),
+                                    tv_nsec,
+                                    msg,
+                                    LogLevels[level])))
+                        else:
+                            break;
+                    self.previous_log_count[level] = count
+            if to_console:
+                to_console.sort()
+                self.logger.write("\n".join(zip(*to_console)[1]+('',)))
+
     def UpdateMethodsFromPLCStatus(self):
-        # Get PLC state : Running or Stopped
-        # TODO : use explicit status instead of boolean
         status = None
         if self._connector is not None:
-            status = self._connector.GetPLCstatus()
+            PLCstatus = self._connector.GetPLCstatus()
+            if PLCstatus is not None:
+                status, log_count = PLCstatus
+                self.UpdatePLCLog(log_count)
         if status is None:
             self._connector = None
             status = "Disconnected"
@@ -1082,20 +1133,17 @@
                                       ("_Disconnect", False)],
                    }.get(status,[]):
                 self.ShowMethod(*args)
+            {"Broken": self.logger.write_error,
+             None: lambda x: None}.get(
+                status, self.logger.write)(_("PLC state is \"%s\"\n")%_(status))
             self.previous_plcstate = status
-            return True
-        return False
+            if self.AppFrame is not None:
+                self.AppFrame.RefreshStatusToolBar()
     
     def PullPLCStatusProc(self, event):
         if self._connector is None:
             self.StatusTimer.Stop()
-        if self.UpdateMethodsFromPLCStatus():
-            
-            status = _(self.previous_plcstate)
-            {"Broken": self.logger.write_error,
-             None: lambda x: None}.get(
-                self.previous_plcstate, self.logger.write)(_("PLC is %s\n")%status)
-            self.AppFrame.RefreshStatusToolBar()
+        self.UpdateMethodsFromPLCStatus()
         
     def RegisterDebugVarToConnector(self):
         self.DebugTimer=None
@@ -1109,7 +1157,6 @@
                 if len(WeakCallableDict) == 0:
                     # Callable Dict is empty.
                     # This variable is not needed anymore!
-                    #print "Unused : " + IECPath
                     IECPathsToPop.append(IECPath)
                 elif IECPath != "__tick__":
                     # Convert 
@@ -1132,9 +1179,6 @@
                 self.TracedIECPath = []
                 self._connector.SetTraceVariablesList([])
             self.IECdebug_lock.release()
-            
-            #for IEC_path, IECdebug_data in self.IECdebug_datas.iteritems():
-            #    print IEC_path, IECdebug_data[0].keys()
 
     def ReArmDebugRegisterTimer(self):
         if self.DebugTimer is not None:
@@ -1179,7 +1223,6 @@
         return IECdebug_data[1]
 
     def UnsubscribeDebugIECVariable(self, IECPath, callableobj):
-        #print "Unsubscribe", IECPath, callableobj
         self.IECdebug_lock.acquire()
         IECdebug_data = self.IECdebug_datas.get(IECPath, None)
         if IECdebug_data is not None:
@@ -1231,7 +1274,6 @@
             WeakCallableDict, data_log, status, fvalue = data_tuple
             #data_log.append((debug_tick, value))
             for weakcallable,(args,kwargs) in WeakCallableDict.iteritems():
-                #print weakcallable, value, args, kwargs
                 function = getattr(weakcallable, function_name, None)
                 if function is not None:
                     if status == "Forced" and cargs[1] == fvalue:
@@ -1261,7 +1303,6 @@
             else:
                 plc_status = None
             debug_getvar_retry += 1
-            #print debug_tick, debug_vars
             if plc_status == "Started":
                 self.IECdebug_lock.acquire()
                 if len(debug_vars) == len(self.TracedIECPath):
@@ -1271,7 +1312,7 @@
                     for IECPath,value in zip(self.TracedIECPath, debug_vars):
                         if value is not None:
                             self.CallWeakcallables(IECPath, "NewValue", debug_tick, value)
-                    self.CallWeakcallables("__tick__", "NewDataAvailable")
+                    self.CallWeakcallables("__tick__", "NewDataAvailable", debug_tick)
                 self.IECdebug_lock.release()
                 if debug_getvar_retry == DEBUG_RETRIES_WARN:
                     self.logger.write(_("Waiting debugger to recover...\n"))
@@ -1299,6 +1340,8 @@
         self.DebugThread = None
 
     def _connect_debug(self): 
+        self.previous_plcstate = None
+        self.previous_log_count = [None]*LogLevelsCount
         if self.AppFrame:
             self.AppFrame.ResetGraphicViewers()
         self.RegisterDebugVarToConnector()
@@ -1331,7 +1374,7 @@
         wx.CallAfter(self.UpdateMethodsFromPLCStatus)
 
     def _Connect(self):
-        # don't accept re-connetion is already connected
+        # don't accept re-connetion if already connected
         if self._connector is not None:
             self.logger.write_error(_("Already connected. Please disconnect\n"))
             return
@@ -1396,17 +1439,18 @@
                 status = _(self.previous_plcstate)
             else:
                 status = ""
-            self.logger.write(_("PLC is %s\n")%status)
+
+            #self.logger.write(_("PLC is %s\n")%status)
             
             # Start the status Timer
             self.StatusTimer.Start(milliseconds=500, oneShot=False)
             
-            if self.previous_plcstate=="Started":
+            if self.previous_plcstate in ["Started","Stopped"]:
                 if self.DebugAvailable() and self.GetIECProgramsAndVariables():
-                    self.logger.write(_("Debug connect matching running PLC\n"))
+                    self.logger.write(_("Debugger ready\n"))
                     self._connect_debug()
                 else:
-                    self.logger.write_warning(_("Debug do not match PLC - stop/transfert/start to re-enable\n"))
+                    self.logger.write_warning(_("Debug does not match PLC - stop/transfert/start to re-enable\n"))
 
     def CompareLocalAndRemotePLC(self):
         if self._connector is None:
@@ -1478,6 +1522,8 @@
             else:
                 self.logger.write_error(_("No PLC to transfer (did build succeed ?)\n"))
 
+        self.previous_log_count = [None]*LogLevelsCount
+
         wx.CallAfter(self.UpdateMethodsFromPLCStatus)
 
     StatusMethods = [