00001 #!/usr/local/bin/python 00002 # 00003 # Copyright 2002 00004 # by 00005 # The Board of Trustees of the 00006 # Leland Stanford Junior University. 00007 # All rights reserved. 00008 # 00009 00010 __facility__ = "Online" 00011 __abstract__ = "GLAST LAT run control class" 00012 __author__ = "R. Claus <Claus@SLAC.Stanford.edu> SLAC - GLAST LAT I&T/Online" 00013 __date__ = "10/11/2002" 00014 __version__ = "$Revision: 2.5 $" 00015 __credits__ = "SLAC" 00016 00017 import LATTE.copyright_SLAC 00018 00019 00020 import os 00021 import sys 00022 import atexit 00023 import threading 00024 import logging as log 00025 from qt import * 00026 00027 from Queue import Queue 00028 from RunControlFSM import RunControlFSM 00029 from RunControlGUIImpl import RunControlGUIImpl 00030 00031 from LATTE.database.gVersions import Gversions 00032 from LATTE.tools.guiBridges import QtThreadBridge, PyThreadBridge 00033 from LATTE.client.gLog import logException 00034 from LATTE.tools.taskEngine import TaskEngine 00035 00036 00037 class ConnectState: 00038 Disconnected = 0 00039 Waiting = 1 00040 Connected = 2 00041 00042 class RunControl(RunControlGUIImpl): 00043 """Class for providing a run control framework 00044 """ 00045 00046 GUI_CMD_INST = QEvent.Type(QEvent.User + 1) 00047 GUI_CMD_FUNC = QEvent.Type(QEvent.User + 2) 00048 GUI_CMD_FUNC_NR = QEvent.Type(QEvent.User + 3) 00049 00050 def __init__(self, common, parent = None, name = None, fl = 0): 00051 RunControlGUIImpl.__init__(self, common, parent, name, fl) 00052 00053 self.gui = self # For backward compatibility 00054 00055 self.__common = common 00056 self.__fsm = None 00057 self.__actionEngine = None 00058 self.__hwVersions = None 00059 self.__queue = None 00060 self.__engine = None 00061 00062 # Set this object to be the client handler 00063 common.getCmdCli().handler(self) 00064 self.__connection_state = ConnectState.Disconnected 00065 #self.StopButton.setEnabled(False) 00066 #self.RunButton.setEnabled(False) 00067 #self.PauseButton.setEnabled(False) 00068 00069 # Set up the GUI bridge 00070 self.initGUIbridges() 00071 00072 self.__actionEngine = TaskEngine("FSMactionEngine") 00073 self.__actionEngine.start() 00074 00075 self.commandSynch = TaskEngine("CommandSynch") 00076 self.commandSynch.start() 00077 00078 self.eventHandler = TaskEngine("EventHandler") 00079 self.eventHandler.start() 00080 00081 # Clean up at exit 00082 self.inShutdown = False 00083 atexit.register(self.__shutdown) 00084 00085 def disconnected(self): 00086 self.__disconnect() 00087 00088 def timedout(self): 00089 self.__disconnect() 00090 00091 def __connection_notthere(self): # GUI thread 00092 #self.StopButton.setEnabled(False) 00093 #self.RunButton.setEnabled(False) 00094 #self.PauseButton.setEnabled(False) 00095 if self.__connection_state == ConnectState.Connected: 00096 log.error("OCS server disconnected, trying to reconnect") 00097 self.__connection_state = ConnectState.Waiting 00098 self.__common.disconnect() # RiC 00099 self.__connect() 00100 00101 def __connection_established(self): # GUI thread 00102 log.info("OCS server is now connected") 00103 #self.StopButton.setEnabled(True) 00104 #self.RunButton.setEnabled(True) 00105 #self.PauseButton.setEnabled(True) 00106 self.__connection_state = ConnectState.Connected 00107 00108 def __connection_failed(self, msg): # GUI thread 00109 if self.__tries == 0: 00110 log.error(msg) 00111 self.__tries += 1 00112 return self.__connection_state 00113 00114 def connectClicked(self): # GUI thread 00115 if self.__connection_state is ConnectState.Disconnected: 00116 self.fileConnectAction.setMenuText("Disconnect") 00117 self.__connection_state = ConnectState.Waiting 00118 self.__tries = 0 00119 self.__connect() 00120 else: 00121 self.fileConnectAction.setMenuText("Connect") 00122 self.__connection_state = ConnectState.Disconnected 00123 self.__disconnect() 00124 00125 def __connect(self): 00126 connecthdlr = threading.Thread(None, self.__connection_handler, 'ConnectHandler',()) 00127 connecthdlr.setDaemon(True) 00128 connecthdlr.start() 00129 00130 def __disconnect(self): 00131 self.execGUImethod(self.__connection_notthere) 00132 00133 def __connection_handler(self): 00134 while (True): 00135 try: 00136 self.__common.connect() 00137 self.execGUImethodNR(self.__connection_established) 00138 break 00139 except RuntimeError, msg: 00140 state = self.execGUImethod(self.__connection_failed, msg) 00141 if state == ConnectState.Disconnected: break 00142 time.sleep(3) 00143 00144 def userId(self): 00145 return 43231 #self.preferences()["userid"] 00146 00147 def __statusMsg(self, msg, tmo): 00148 self.statusBar().message(msg, tmo) 00149 00150 def __shutdown(self): 00151 """\brief Clean up and shut down the various threads. 00152 """ 00153 # This method expects to be executed from the GUI thread. 00154 00155 self.inShutdown = True 00156 00157 # Bring the system into the RESET state 00158 self.doReset() 00159 00160 # Use the following non-NR call to wait for the reset to finish 00161 self.execGUImethod(self.statusBar().message, "Exiting") 00162 00163 # Shut down the task engines 00164 self.__actionEngine.shutdown() 00165 self.eventHandler.shutdown() 00166 self.commandSynch.shutdown() 00167 00168 # After the FSM Action Engine has shut down we can delete the FSM 00169 if self.__fsm: 00170 del self.__fsm 00171 self.__fsm = None 00172 00173 def __start(self, applName): 00174 ## Import the application 00175 #(appPath, appFile) = os.path.split(applName) 00176 #(appBase, appExt) = os.path.splitext(appFile) 00177 #appStuff = self.__bml.find_module(appBase, (appPath,)) 00178 #if appStuff is None: 00179 # msg = 'Application %s is invalid' % applName 00180 # logging.error(msg) 00181 # return msg 00182 #module = self.__bml.load_module(appBase, appStuff) 00183 #if 'userApplication' not in dir(module): 00184 # msg = 'Application %s is missing a userApplication class' % applName 00185 # logging.error(msg) 00186 # return msg 00187 00188 import rcUtil 00189 try: 00190 dontReloadList = self.__common.options().noreload 00191 (module, filename) = rcUtil.importModule(appName=applName, reload=1, dontReloadList=dontReloadList) 00192 except Exception, e: 00193 logException() 00194 self.execGUImethodNR(self.__statusMsg, e[0], 0) 00195 return 00196 00197 # Instantiate the application 00198 app = module.userApplication(self, self.__common.prefMan().userId(), 00199 self.__common.options().evtdebug is not None) 00200 log.info("Application %s loaded" % app.getName()) 00201 00202 # Set up the Finite State Machine 00203 self.__fsm = RunControlFSM(app) 00204 00205 self.execGUImethodNR(self.__statusMsg, '%s: Loaded application %s' % 00206 (self.__fsm.current_state, applName), 0) 00207 00208 def SetupClicked(self): 00209 RunControlGUIImpl.SetupClicked(self) 00210 self.__actionEngine.spawn(self.__setup) 00211 00212 def __setup(self): 00213 if self.__fsm is not None: 00214 self.__fsm.process('SETUP') 00215 self.__statusMsg(self.__fsm.current_state, 0) 00216 00217 def TeardownClicked(self): 00218 RunControlGUIImpl.TeardownClicked(self) 00219 self.__actionEngine.spawn(self.__teardown) 00220 00221 def __teardown(self): 00222 if self.__fsm is not None: 00223 self.__fsm.process('TEARDOWN') 00224 self.__statusMsg(self.__fsm.current_state, 0) 00225 00226 def StartRunClicked(self): 00227 RunControlGUIImpl.StartRunClicked(self) 00228 self.__actionEngine.spawn(self.__run) 00229 00230 def __run(self): 00231 if self.__fsm is not None: 00232 self.__fsm.process('START_RUN') 00233 self.__statusMsg(self.__fsm.current_state, 0) 00234 00235 def StopRunClicked(self): 00236 RunControlGUIImpl.StopRunClicked(self) 00237 self.__actionEngine.spawn(self.__stoprun) 00238 00239 def __stoprun(self): 00240 if self.__fsm is not None: 00241 self.__fsm.process('STOP_RUN') 00242 self.__statusMsg(self.__fsm.current_state, 0) 00243 00244 def PauseClicked(self): 00245 RunControlGUIImpl.PauseClicked(self) 00246 self.__actionEngine.spawn(self.__pause) 00247 00248 def __pause(self): 00249 if self.__fsm is not None: 00250 self.__fsm.process('PAUSE') 00251 self.__statusMsg(self.__fsm.current_state, 0) 00252 00253 def ResumeClicked(self): 00254 RunControlGUIImpl.ResumeClicked(self) 00255 self.__actionEngine.spawn(self.__resume) 00256 00257 def __resume(self): 00258 if self.__fsm is not None: 00259 self.__fsm.process('RESUME') 00260 self.__statusMsg(self.__fsm.current_state, 0) 00261 00262 def StopClicked(self): 00263 RunControlGUIImpl.StopClicked(self) 00264 self.__actionEngine.spawn(self.__stop) 00265 00266 def __stop(self): 00267 if self.__fsm is not None: 00268 self.__fsm.process('STOP') 00269 self.__statusMsg(self.__fsm.current_state, 0) 00270 00271 00272 def load(self): 00273 if self.__fsm is not None and self.__fsm.current_state != 'RESET': 00274 print "Applications are loadable only in the RESET state" 00275 self.__statusMsg('%s: Applications are loadable only in the RESET state' % 00276 (self.__fsm.current_state), 0) 00277 return 00278 00279 applName = RunControlGUIImpl.load(self) 00280 if applName is None: 00281 if self.__fsm is not None: 00282 self.__statusMsg('%s: Loading aborted' % (self.__fsm.current_state), 0) 00283 else: 00284 self.__statusMsg('Loading aborted', 0) 00285 return 00286 00287 if self.__fsm is not None: 00288 self.__shutdown() 00289 00290 # Start the application in its own thread after the reset from above 00291 # (if any) completes. 00292 self.__actionEngine.spawn(self.__start, str(applName)) 00293 00294 #msg = self.__start(str(applName)) 00295 #if msg is None: 00296 # self.__statusMsg('%s: Loaded application %s' % 00297 # (self.__fsm.current_state, applName), 0) 00298 #else: 00299 # self.__statusMsg(msg, 0) 00300 00301 def fileExit(self): 00302 self.close() 00303 00304 def ExitButtonClicked(self): 00305 self.close() 00306 00307 def doReset(self): 00308 """Emit a Reset button click under program control. 00309 Can be done from a subthread.""" 00310 self.doEmit(self.ResetButton) 00311 00312 def doStop(self): 00313 """Emit a Stop button click under program control. 00314 Can be done from a subthread.""" 00315 00316 # Ignore the doStop for this RunControl GUI 00317 return 00318 self.doEmit(self.StopButton) 00319 00320 def doStopRun(self): 00321 """Depricated version of doStop().""" 00322 self.doStop() 00323 00324 def doRun(self): 00325 """Emit a Run button click under program control. 00326 Can be done from a subthread.""" 00327 self.doEmit(self.RunButton) 00328 00329 def doPause(self): 00330 """Emit a Pause button click under program control. 00331 Can be done from a subthread.""" 00332 self.doEmit(self.PauseButton) 00333 00334 def doEmit(self, obj, signal = SIGNAL("clicked()"), args = ()): 00335 """Emit a signal, possibly from a subthread. 00336 By default the signal is 'clicked()' with no arguments.""" 00337 if self.isGUIthread(): 00338 obj.emit(signal, args) 00339 else: 00340 self.execGUImethodNR(obj.emit, signal, args) 00341 00342 def normalCursor(self): 00343 self.execGUImethodNR(self.setCursor, QCursor(QWidget.ArrowCursor)) 00344 00345 def waitCursor(self): 00346 self.execGUImethodNR(self.setCursor, QCursor(QWidget.WaitCursor)) 00347 00348 def getHardwareVersions(self, lat=None): 00349 if self.__hwVersions is None: 00350 self.__hwVersions = Gversions.getHardwareVersions(lat) 00351 return self.__hwVersions 00352 00353 def clearHardwareVersionsCache(self): 00354 self.__hwVersions = None 00355 00356 def getFSM(self): 00357 return self.__fsm 00358 00359 def setLAT(self, lat): 00360 self.__lat = lat 00361 00362 def initGUIbridges(self): 00363 self.__thread = threading.currentThread() 00364 inQueue = Queue() 00365 outQueue = Queue() 00366 self.__qtBridge = QtThreadBridge("QtBridge", outQueue, inQueue) 00367 self.__qtBridge.start() 00368 self.__pyBridge = PyThreadBridge("PyBridge", inQueue, outQueue) 00369 00370 def isGUIthread(self): 00371 return threading.currentThread() == self.__thread 00372 00373 def customEvent(self, e): 00374 # This method overrides the QObject base class's 00375 data = e.data() 00376 qtBridge = data[-1] 00377 if e.type() == RunControl.GUI_CMD_INST: 00378 qtBridge.pushResponse(data[0](*data[1:-2], **data[-2])) 00379 elif e.type() == RunControl.GUI_CMD_FUNC: 00380 qtBridge.pushResponse(data[0](*data[1:-2], **data[-2])) 00381 elif e.type() == RunControl.GUI_CMD_FUNC_NR: 00382 data[0](*data[1:-2], **data[-2]) 00383 00384 def createGUI(self, obj, *args, **kwargs): 00385 """Method used for instantiating a GUI from a non-GUI subthread""" 00386 self.__pyBridge.pushEvent((RunControl.GUI_CMD_INST, self, (obj,)+args+(kwargs,))) 00387 return self.__pyBridge.pullResponse() 00388 00389 def execGUImethodNR(self, func, *args, **kwargs): 00390 """Method used for executing a GUI function from a non-GUI thread. 00391 Use this method when no (useful) response is expected or 00392 when waiting for one could cause a deadlock. Any response from the 00393 function is lost.""" 00394 if self.inShutdown: return # Don't try to update the GUI during shutdown 00395 self.__pyBridge.pushEvent((RunControl.GUI_CMD_FUNC_NR, self, (func,)+args+(kwargs,))) 00396 00397 def execGUImethod(self, func, *args, **kwargs): 00398 """Method used for executing a GUI function from a non-GUI thread. 00399 Use this method when a response is expected from the function and 00400 when it is appropriate to wait for it (no deadlock arises)""" 00401 self.__pyBridge.pushEvent((RunControl.GUI_CMD_FUNC, self, (func,)+args+(kwargs,))) 00402 return self.__pyBridge.pullResponse()