Qudi
|
In order to create a task you can choose between 2 different categories.
InterruptableTask or PrePostTask
You have to inherit one of those classes (both are situated in /logic/generic_tasks.py
) for your tasks and re-implement the class specific methods for your needs. The tasks basically contain a reference to the object you are aiming to manipulate.
InterruptableTask are those, which are used to perform the actual task. According to the name of this class, you should design the task such, that it can be interrupted and stopped at any time.
To manage the behaviour of that, a finite state machine construction is used, which defines all possible states of the InterruptableTask object and all valid state transitions (that is the essence of a state machine!). The Graph below shows an ASM (algorithmic state machine) chart of our underlying state machine:
[Graph InterruptableTask here]
PrePostTask are those which are done before or follow after the actual InterruptableTask. The state machine description of those tasks are quite simple, as it can be seen in the Graph below.
[Graph PrePostTask here]
In general, the PrePostTasks are quite small steps (like e.g. tilting the mirror, switching on or off of a device for the actual measurement).
So now you have two possibilities to perform a manipulation.
For really basic and small tasks (which cannot or are not needed to be separated into smaller tasks) the smartest way to implement task is via one method, which can be called from one object. That idea is only restricted to the scenario, if your task actions are limited to one object only! This very method will handle then all needed changes within the object and will perform the desired task. That idea will facilitate maintainability of the task, since it only depends on the internal implementation of the addressed method. That makes it traceable for the user.
As soon as your task will interact with many object (i.e. retrieve and pass values from one object to the other and react on the obtained parameter) then all the task implementation and object access will be done within the task and not within the other objects, which the task is intended to control.
Think about an obvious/general way of how to return the data after a method, i.e. the structure of the return value, maybe not just a bunch of elements in a list, where you do not know, which element of a list is representing what. Think of using pythons dictionaries, since you can assign to a generic and understandable name to return values. Then you only have to fix yourself on a proper keywords which you will use in the dict.
If the way you are calling and controlling this object is general, then other people will potentially use it.
Custom tasks are logic operations, and therefore saved in the folder /logic/tasks
folder. There you can also get an idea how implementation of possible tasks can look like.
Within a configuration file, a custom task configuration may look like that:
tasklogic: module.Class: 'taskrunner.TaskRunner' tasks: dummytask: module: 'dummy' preposttasks: ['ppdummy'] dummyinterruptor: module: 'dummy' pausetasks: ['dummytask'] preposttasks: ['ppdummy2'] ppdummy: module: 'prepostdummy' ppdummy2: module: 'prepostdummy' scannerLocationRefocus: module: 'refocus' preposttasks: ['fliplasermirror'] pausetasks: ['scan', 'odmr'] needsmodules: optimizer: 'optimizerlogic' config: initial: [1, 1, 1] fliplasermirror: module: 'flipmirror' needsmodules: switchlogic: 'switch' config: sequence: [('mydummyswitch1', 1, True), ('mydummyswitch1', 2, True), ('mydummyswitch2', 1, True)]
Your individual tasks can be defined below the tasks
keyword. The naming procedure is pretty similar to the working principle of our configuration files (=> How to use a configuration file.
<identifier1>: module: '<module_name1>' preposttasks: ['<identifier2>'] <identifier2>: module: '<module_name2>'
You choose a name <identifier1>
, how the task should be called, and define with the keyword module
which task module you want to take. This module (a python file) with name <module_name1>
must be present in the directory.
Within the file <module_name1>.py
a class must exist with the name Task
, which inherits either the InterruptableTask or PrePostTask class. An example may look like that:
from logic.generic_task import InterruptableTask class Task(InterruptableTask): def __init__(self, name, runner, references, config): """ A task has the following constructor: @param str name: unique task name @param object runner: reference to the TaskRunner managing this task @param dict references: a dictionary of all required modules @param dict config: configuration dictionary """ super().__init__(name, runner, references, config) print('Task {} added!'.format(self.name)) print(self.config) def startTask(self) # Reimplement that! return def runTaskStep(self) # Reimplement that! return def pauseTask(self) # Reimplement that! return def resumeTask(self) # Reimplement that! return def cleanupTask(self) # Reimplement that! return
This example above must reimplement each method of the class InterruptableTask. That would be right now the methods:
The additional methods
can be, but have not to be reimplemented, if no special requirements are going to be set for that task. These methods are used to check, whether the present task can be paused if it is in the running state.
Therefore, these methods provides you with e.g. an external checking procedure, which you can implement for the specific task. Consequently the return value has to be of boolean type, i.e. either True
or False
.
The PrePostTask implementation is much simpler. An example of <module_name2>
might look like that:
from logic.generic_task import PrePostTask class Task(PrePostTask): def __init__(self, name, runner, references, config): """ A task has the following constructor: @param str name: unique task name @param object runner: reference to the TaskRunner managing this task @param dict references: a dictionary of all required modules @param dict config: configuration dictionary """ super().__init__(name, runner, references, config) print('PrePost init task {}'.format(name)) print(self.config) def preExecute(self): # Reimplement that! return def postExecute(self): # Reimplement that! return
All the tasks are handled by the Logic Module TaskRunner.py.