The PyCRAM plan language is a domain-specific language in Python designed to help with the creation of robot control plans. The plan language is implemented as macros which bring features like parallel execution, failure handling and sequential execution. All of these features will be explained in detail later on. All macros of the plan language can terminate in one of two states, either the succeed of they fail. The conditions if a macro succeeds or fails changes depending on the macro.
The plan language is implemented using a slightly modified version of MacroPy, which is included in the PyCRAM repo as a submodule. Macros using MacroPy work by transforming the Abstract Syntax Tree (AST), this way new code or data can be inserted.
If you are using the plan language macros in your code you have to pay attention to the way the macro is imported to the file. Because of the way MacroPy works you always have to import the 'macros' object from the language.py file first before importing any macros like this:
from pycram.language import macros, par
The next thing you should keep in mind is that you can't directly execute the file which imports the macro of the plan language. Instead you have to create another file which imports the activation method of MacroPy and then executes the code in the file which contains the macro. This can then look like this:
import macropy.active import demo
In this case demo is the file which imports the 'par' macro of the plan language.
There are a total of five expressions available in the plan language adding to this is the error handling which adds more control over the handling of errors than the standard Python way. The expressions can be categorized in 3 categories, sequential execution, parallel execution and error handling. All expressions will catch errors that may be raised during execution, so the program isn't stopped, and update their state correspondingly. The state of an expression can either be SUCCEEDED or FAILED, the conditions if an expression succeeds or fails are different for every expression. The raised errors can be stored in a list for later inspection of what went wrong.
To store the errors in a list a variable has to be passed to the expression, this is optional and does not have to be done for the expression to work.
The seq expression executes all given statements after another, if one of the statements raises an error the others will not be executed and the state will be updated to FAILED. An example on how to use the seq expression can be seen below.
from pycram.language import macros, par errors = [] with seq(errors) as state: statement1 statement2
Here you can see that the errors which occur during execution will be saved in a variable which is handed to the expression. The statements will be executed after another so if an error occurs in the execution of statement1 this error will be saved in the errors variable and the expression will be terminated.
It is also possible to use the expression without an error list, an example of this can be seen below.
from pycram.language import macros, par with seq as state: statement1 statement2
The expression try_in_order executes all given statements in order but unlike seq this expression will stop if one of the given statements succeeds. For this expression there is also the possibility to use a list to store raised errors, this is an optional feature. The expression is used similar to the seq expression.
from pycram.language import macros, try_in_order with try_in_order as state: statement1 statement2
The expression par executes every given statement in a separate thread, if one of these threads raises an error the state of this expression will be set to failed. However, the other threads will still be finished because Python doesn't support interrupting threads and so the threads can clean up allocated memory.
This expression also supports the possibility of passing a variable to save a list of raised errors. This is, like in all expressions, an optional feature.
from pycram.language import macros, par with par as state: statement1 statement2
This will create two new threads and execute statement1 in the first thread and statement2 in the second one. If you want to group multiple statements together because they should be executed in the same thread you have to put them in a block. For this you can, for example, use the seq expression.
from pycram.language import macros, par, seq with par as state: statement1 with seq as s: statement2 statement3
If you want all statements to be executed and don't want the behaviour of the sequential expressions you can use an if statement.
from pycram.language import macros, par, seq with par as state: statement1 if True: statement2 statement3
This grouping of statements works for all parallel expressions.
This expression will also execute all given statements in separate threads. The state will be set to SUCCEED if one of the statements executes without an error and FAIL if all statements raise an error during execution.
from pycram.language import macros, try_all with try_all as state: statement1 statement2 statement3
The expression will create a separate thread for every statement execute them and afterwards collect them again so the user doesn't have to worry about thread management.
This expression also creates a thread for every given statement and executes them in parallel. The state will be set to SUCCEEDED if one statement is executed without raising an error and FAILED if one of the statements raises an error during execution.
from pycram.language import macros, pursue with pursue as state: statement1 statement2 statement3
The PyCRAM plan language comes with its own way of handling errors which is combined with the standard python way and. There are two things which the plan language adds to error handling. Firstly, a retry function which executes the code again, this enables the developer to implement recovery behaviour and to repose the robot and then try the failed code again. Secondly, a retry counter which determines how often the retry function can be called. If the passed counter is zero, negative or no counter is passed, the retry function can be called an unlimited time.
Below you can see examples on the different ways the error handling can be used.
from pycram.language import failure_handling with failure_handling(): try: statement1 except Exception as e: retry()
The retry function is automatically implemented by the failure handling and can be used within the scope of the failure_handling.
from pycram.language import failure_handling with failure_handling(3): try: statement1 except Exception as e: retry()
In this case we pass a number to the failure handling, this number defines the number of retries meaning how often the retry function is maximally called.