diff -r 6e205c1f05a0 -r eee7625de1f7 ProjectController.py --- 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 = [