Quick Guide to Developing Run Control Enabled Applications
Selim Tuvi firstname.lastname@example.org
Disclaimer: The reader of this document is assumed to be familiar with object-oriented programming and the Python programming language.
Introduction to RunControl:
The following is a screenshot of the Run Control graphical user interface:
After Run Control is launched either using the icon located under the ‘GLAST I&T Online’ program group in the ‘Start’ menu or using the provided batch file ‘runcontrolmain.bat’ the operator is presented with the above application. The first thing the operator needs to do is enter his/her user name in the provided text entry box.
The next optional step is to check or change the preferences by choosing ‘Preferences” from the ‘Edit’ menu. The following dialog will display:
This is where the user can change file input/output locations and to enable/disable certain options.
After this the operator can
select the test application by clicking on the select application ()
button to the left of the “Select Application”
After the application is loaded the Run Control framework is put in the ‘RESET’ state. The framework uses a finite state machine to go through the different transitions. The state and transition diagram is shown below:
At this point the application can be started by clicking on the start () button. The Run Control framework will automatically ask the operator to load a schema. This allows the same test application to be run with different schemas.
Depending on the application
The mapping between the transitions and the methods that needs to be defined in the test application are shown in the following table:
Writing a Pyhon program that conforms to the RunControl framework:
For facilitating the learning a sample application called “testAppEvt.py” that is located in the RunControl/applications directory will be used. Some code from that application may be omitted here to simplify things. Some of the code here is optional and should not be assumed that all applications have to be coded the same way.
A Run Control enabled application always defines a class named “userApplication” which inherits from “rcTransitions”.
There are three parameters passed to the __init__ method:
The __init__ method is responsible for initialization of the application components. Usually this involves setting up event and command synchronization threads if any are required. Here is a simple __init__ method:
def __init__(self, gui, userId, debug):
rcTransitions.__init__(self, gui, userId, debug)
self.__eventSem = threading.Semaphore(0)
self.__cmdSynchSem = threading.Semaphore(0)
if gui is None:
self.__arg = userArgText()
self.__arg = userArgument(gui, 'test1', 1)
In the above example first the base class __init__ method is called to initialize the Run Control framework. Then logging is done to record that the init method has been executed. After that two semaphores are declared to control the synchronization between the triggering of the events and the processing of the events. The reason for this is that at the beginning of the run, Run Control creates an event handler thread which is used to call the application’s process() method for each incoming event and if the application is doing its own triggering with the glt.CMD_SELF_TRIGGER command it needs to have a way of controlling the processing so that all event handling will complete before the next event comes in.
The last block of code in the __init__ method initializes the object responsible for asking the user how many events are desired. If the gui is supplied then the GUI dialog can be shown otherwise a console input is done.
The above method is used to return the name of the application. This value is used as the “TestName” in the test report. In the above example it defaults to the application source name (without the .py).
tem = self.lat.downTEM(0)
if tem is not None:
# clear TEM stats reg
tem.COMMAND_RESPONSE = 0
# clear TEM status reg
tem.STATUS = 0
tem.DATA_MASKS = 0x10FF
# TEM config reg # set TEM timeout
tem.CONFIGURATION = 0
# A state transition can be rejected by not returning None
The setup() method is called by the Run Control GUI when the “Run” button is clicked. This is done after the schema is selected and loaded by the operator. At this point Run Control has loaded the application and the schema and has connected to the command and event servers. The self.lat object contains a pointer to the LAT hierarchy. In the setup() method you can put your hardware initialization code or other things you need to do before the event data taking starts. By returning a value other than None in this method you can notify RunControl that something is wrong and that you don’t wish to continue.
cnt = self.__arg.getValue("Enter number of triggers to take")
#~ self.glt.OPTIONS = 0xA05
self.glt.DESTINATION = 5
self.glt.MARKER = 0
self.glt.ZERO_SUPPRESS = 1
self.glt.FOUR_RANGE_READOUT = 0
self.glt.TACK = 1
self.glt.CAL_STROBE = 0
# Spawn a thread to synchronize commands with events
# This must happen after triggers are enabled
self.__cmdSynchQuit = FALSE
cmdSynch = threading.Thread(None, self.__commandSynch, 'CmdSynch', (cnt,))
self.__cmdSynch = cmdSynch
# A state transition can be rejected by not returning None
The startRun() method is where the operator is asked of any runtime specific parameters such as the number of events to take.
This is also a good place to setup GLT for triggering and to spawn a thread that will actually do the triggering. As you can see the argument(s) inputted by the operator can be passed to the commanding thread for use by that thread.
As in the setup() method Run Control system can be notified of an error condition by not returning None.
self.__cmdSynchQuit = TRUE
The stopRun() method is called when the operator clicks on the “Stop” button. The main thing to do here is return a completion status. This status is recorded in the test report and can be one of the following:
COMPL_STATUS_UNDEFINED = -1
COMPL_STATUS_ABORTED = -2
COMPL_STATUS_ERROR = -3
COMPL_STATUS_SUCCESS = 0
In addition to the above the event triggering thread should be terminated by setting a flag and releasing the event thread semaphore. The last line which calls the join() method on the thread forces the execution to wait until the thread terminates.
Now let’s take a look at the process() method:
def process(self, (status, buffer)):
"Method called back for each data event taken"
if status == 0:
evtCli = self.evtCli
print "Event Size =", evtCli.evt_size
print "Event Status =", evtCli.evt_status
ts = evtCli.evGetTimestamp()
print "Event Timestamp (GMT)=", asctime(gmtime(ts)), \
"and", int(modf(ts)*1000), "ms"
contList = evtCli.getContributionList()
for cId in contList:
elif cId == 'ACD':
# Get next event triggered
Each time an event is received by the Run Control system the process() method is called. The parameters passed to the method are status which should be checked before calling the event parser routines, and the buffer which contains the event datagram. The reason status and buffer is passed in as a tuple is to maintain backward compatibility with the old test programs where buffer was the only expected parameter.
The event client object is exposed as self.evtCli and this object can be used to call the event parser routines as shown above. Please refer to the event client documentation for more information on these routines.
Finally in order for the triggering to resume the event semaphore should be released.
Command synchronization thread:
The typical way of triggering an event and processing the event in the same python application is to use threads. In this case the event thread is taken care of in the Run Control framework but unless the events are externally triggered, a command synchronization thread is needed:
def __commandSynch(self, count):
"Method called by the command synchronization task"
glt = self.glt
eventSem = self.__eventSem
# Drain the semaphore release count
# Handles the case when the stop run release collided with a trigger release
while eventSem.acquire(0): pass
t0 = time.time()
cnt = 0
while cnt < count and not self.__cmdSynchQuit:
cnt += 1
glt.CMD_SELF_TRIGGER = 1 # Issue an internal trigger
eventSem.acquire() # Wait for the event to be processed
dT = time.time() - t0
if dT == 0.0: dT = 0.000001
log.info("%s processed %d events in %.3f seconds = %.1f events/second" % \
(self.getName(), cnt, dT, self.evtCnt/dT))
# Get out of waiting when in standalone mode
It is required to make sure that the event semaphore is decremented back to zero. Since these are counting semaphores, each release() would increment the count and each acquire() would decrement the count. The while eventSem.acquire(0) loop drains the semaphore count back to zero in case there have been more than one releases by stopRun() and process().
The thread starts by entering a while loop that is conditioned to exit when the trigger count equals the parameter specified by the user or when the quit flag is set.
Inside the loop it increments the local counter, initiates the trigger and then does an acquire() on the event semaphore to wait for the event to be received and processed. This ensures that the process() method finishes its event processing before another trigger can be issued.
After the loop exits it is up to the script to log whatever it wants or do some needed cleanup.
Finally if the application is running in standalone mode, meaning that the script is being run outside the Run Control GUI, the application has to make sure the thread has completed its processing. The standalone mode code calls the following routines to wait until the thread completes:
if __name__ == "__main__":
At certain times it may be desirable to run the test application without a GUI present. In such cases the following needs to be added to the test application:
# Standalone mode:
if __name__ == "__main__":
ua = userApplication(None, 321, 0)
Since the application is not governed by the GUI certain extra processing needs to be done. The first thing is configuring and initializing the logger.
Then test application is constructed by passing it None for the gui, and then the user id and debug mode. Note that none of the preferences will be in effect since it is being run in standalone mode. A future release should mirror all the functionality of the GUI mode by adding additional methods.
Then Run Control functions are used to guide the application through the usual transitional steps.
Because of the limited functionality of the standalone mode it is strongly advised that the applications are executed this way for debugging purposes only.