Understanding contingency-handlers
Overview
This tutorial demonstrates how contingency-handlers
can be used to ‘react’ to failures
which occur during execution. In the following examples custom SequenceNodes
are used.
Create an ActionNode with failures
Create a file named sequence_with_contingencies.py
with following content.
Or use the provided file: sequence_with_contingencies.py
First of all, an ActionNode
which ‘can’ fail is required. Therefore, the
AddTwoNumbersAction
is extended that it can complete with FAILURE
. This
extended node is called AddTwoNumbersActionWithFailures
. It is implemented that
a failure can be provoked in case that one or both input parameters are missing or
in case the result of the sum of the two parameters is greater than ten. These are,
of course, just some ‘artificial’ failures to beeing able to demonstrate the technics
of contingency-handlers.
1from carebt import ActionNode
2from carebt import ContingencyHistoryEntry
3from carebt import NodeStatus
4from carebt import SequenceNode
5from carebt.examples.simple_sequence import CreateRandomNumberAction
6from carebt.examples.simple_sequence import PrintNumberAction
7
8
9class AddTwoNumbersActionWithFailures(ActionNode):
10 """The `AddTwoNumbersActionWithFailures` example node.
11
12 The `AddTwoNumbersActionWithFailures` demonstrates a careBT `ActionNode`
13 with two input parameters and one output parameter. It takes the two inputs,
14 adds them and returns the result. Furthermore, this node can complete with
15 `FAILURE`. This happend in case that one or both input parameters are missing
16 or that the result of the sum is greater than ten.
17
18 Input Parameters
19 ----------------
20 ?x : int
21 The first value
22 ?y : int
23 The second value
24
25 Output Parameters
26 -----------------
27 ?z : int
28 The sum of ?x and ?y
29
30 Contingencies
31 -------------
32 FAILURE:
33 ONE_PARAM_MISSING
34 One input parameter is missing.
35
36 BOTH_PARAMS_MISSING
37 Both input parameters are missing.
38
39 RESULT_TOO_LARGE
40 The result is greater than 10.
41
42 """
43
44 def __init__(self, bt_runner):
45 super().__init__(bt_runner, '?x ?y => ?z')
46
47 def on_init(self) -> None:
48 if(self._x is None and self._y is None):
49 self.set_status(NodeStatus.FAILURE)
50 self.set_contingency_message('BOTH_PARAMS_MISSING')
51 elif(self._y is None):
52 self.set_status(NodeStatus.FAILURE)
53 self.set_contingency_message('ONE_PARAM_MISSING')
54
55 def on_tick(self) -> None:
56 self._z = self._x + self._y
57 if(self._z > 10):
58 print('AddTwoNumbersActionWithFailures: calculating: '
59 + f'{self._x} + {self._y} = {self._z} -> RESULT_TOO_LARGE')
60 self.set_status(NodeStatus.FAILURE)
61 self.set_contingency_message('RESULT_TOO_LARGE')
62 else:
63 print('AddTwoNumbersActionWithFailures: calculating: '
64 + f'{self._x} + {self._y} = {self._z}')
65 self.set_status(NodeStatus.SUCCESS)
The code explained
The AddTwoNumbersAction
was already introduced in Writing a node with parameters and is
now extended to provide some failures. This extended node is called AddTwoNumbersActionWithFailures
.
In the on_init
function two different contingency situations are implemented. The first one
if both input parameters are missing. In this case the node completes with FAILURE
and provides
the contingency-message ‘BOTH_PARAMS_MISSING’. And the second one if one input parameter is missing.
In this case the node completes with FAILURE
and provides the contingency-message ‘ONE_PARAM_MISSING’.
def on_init(self) -> None:
if(self._x is None and self._y is None):
self.set_status(NodeStatus.FAILURE)
self.set_contingency_message('BOTH_PARAMS_MISSING')
elif(self._y is None):
self.set_status(NodeStatus.FAILURE)
self.set_contingency_message('ONE_PARAM_MISSING')
In the on_tick
function the result of the calculation is checked whether it is greater than ten. This is,
of course, just for demo purposes. In case _z is greater than ten the node fails and provides the
contingency-message ‘RESULT_TOO_LARGE’.
def on_tick(self) -> None:
self._z = self._x + self._y
if(self._z > 10):
print('AddTwoNumbersActionWithFailures: calculating: '
+ f'{self._x} + {self._y} = {self._z} -> RESULT_TOO_LARGE')
self.set_status(NodeStatus.FAILURE)
self.set_contingency_message('RESULT_TOO_LARGE')
else:
print('AddTwoNumbersActionWithFailures: calculating: '
+ f'{self._x} + {self._y} = {self._z}')
self.set_status(NodeStatus.SUCCESS)
Run the example
Start the Python interpreter and run the AddTwoNumbersActionWithFailures
node.
The log-level is set to INFO to see some more details on the execution:
>>> import carebt
>>> from carebt import LogLevel
>>> from carebt.examples.sequence_with_contingencies import AddTwoNumbersActionWithFailures
>>> bt_runner = carebt.BehaviorTreeRunner()
>>> bt_runner.get_logger().set_log_level(LogLevel.INFO)
>>> bt_runner.run(AddTwoNumbersActionWithFailures, '3 5 => ?x')
2022-03-05 17:09:01 INFO creating AddTwoNumbersActionWithFailures
AddTwoNumbersActionWithFailures: calculating: 3 + 5 = 8
2022-03-05 17:09:01 INFO ---------------------------------------------------
2022-03-05 17:09:01 INFO bt execution finished
2022-03-05 17:09:01 INFO status: NodeStatus.SUCCESS
2022-03-05 17:09:01 INFO contingency-message:
2022-03-05 17:09:01 INFO ---------------------------------------------------
>>> bt_runner.run(AddTwoNumbersActionWithFailures, '3 => ?x')
2022-03-05 17:11:53 INFO creating AddTwoNumbersActionWithFailures
2022-03-05 17:11:53 WARN AddTwoNumbersActionWithFailures takes 2 argument(s), but 1 was/were provided
2022-03-05 17:11:53 WARN ---------------------------------------------------
2022-03-05 17:11:53 WARN bt execution finished
2022-03-05 17:11:53 WARN status: NodeStatus.FAILURE
2022-03-05 17:11:53 WARN contingency-message: ONE_PARAM_MISSING
2022-03-05 17:11:53 WARN ---------------------------------------------------
>>> bt_runner.run(AddTwoNumbersActionWithFailures, '=> ?x')
2022-03-05 17:12:35 INFO creating AddTwoNumbersActionWithFailures
2022-03-05 17:12:35 WARN AddTwoNumbersActionWithFailures takes 2 argument(s), but 0 was/were provided
2022-03-05 17:12:35 WARN ---------------------------------------------------
2022-03-05 17:12:35 WARN bt execution finished
2022-03-05 17:12:35 WARN status: NodeStatus.FAILURE
2022-03-05 17:12:35 WARN contingency-message: BOTH_PARAMS_MISSING
2022-03-05 17:12:35 WARN ---------------------------------------------------
>>> bt_runner.run(AddTwoNumbersActionWithFailures, '7 5 => ?x')
2022-03-05 17:12:59 INFO creating AddTwoNumbersActionWithFailures
AddTwoNumbersActionWithFailures: calculating: 7 + 5 = 12 -> RESULT_TOO_LARGE
2022-03-05 17:12:59 WARN ---------------------------------------------------
2022-03-05 17:12:59 WARN bt execution finished
2022-03-05 17:12:59 WARN status: NodeStatus.FAILURE
2022-03-05 17:12:59 WARN contingency-message: RESULT_TOO_LARGE
2022-03-05 17:12:59 WARN ---------------------------------------------------
Create a sequence without contingency-handling
In this example the SimpleSequence
node is implemented which has the two children AddTwoNumbersActionWithFailures
and PrintNumberAction
. The AddTwoNumbersActionWithFailures
is the one from above which can complete with failures.
Add the following content to sequence_with_contingencies.py
.
Or use the provided file: sequence_with_contingencies.py
1class SimpleSequence(SequenceNode):
2 """The `SimpleSequence` example node.
3
4 The `SimpleSequence` shows what happens if a child node in the sequence fails.
5 The used `AddTwoNumbersActionWithFailures` can fail in case that one or both
6 input parameters are missing or that the result of the sum is greater than ten.
7 These contingencies are not handled, and thus forwarded to the `SequenceNode`
8 and finally to the `bt_runner`.
9
10 Input Parameters
11 ----------------
12 ?a : int
13 The first value
14 ?b : int
15 The second value
16
17 Output Parameters
18 -----------------
19 ?c : int
20 The result
21
22 """
23
24 def __init__(self, bt_runner):
25 super().__init__(bt_runner, '?a ?b')
26
27 def on_init(self) -> None:
28 self.append_child(AddTwoNumbersActionWithFailures, '?a ?b => ?c')
29 self.append_child(PrintNumberAction, '?c')
The code explained
The SimpleSequence
has two input parameters which are directly used as inputs for the
AddTwoNumbersActionWithFailures
.
Run the example
Start the Python interpreter and run the SimpleSequence
node:
>>> import carebt
>>> from carebt import LogLevel
>>> from carebt.examples.sequence_with_contingencies import SimpleSequence
>>> bt_runner = carebt.BehaviorTreeRunner()
>>> bt_runner.get_logger().set_log_level(LogLevel.INFO)
>>> bt_runner.run(SimpleSequence, '2 5')
2022-03-05 17:14:21 INFO creating SimpleSequence
2022-03-05 17:14:21 INFO creating AddTwoNumbersActionWithFailures
AddTwoNumbersActionWithFailures: calculating: 2 + 5 = 7
2022-03-05 17:14:21 INFO creating PrintNumberAction
PrintNumberAction: number = 7
2022-03-05 17:14:21 INFO finished SimpleSequence
2022-03-05 17:14:21 INFO ---------------------------------------------------
2022-03-05 17:14:21 INFO bt execution finished
2022-03-05 17:14:21 INFO status: NodeStatus.SUCCESS
2022-03-05 17:14:21 INFO contingency-message:
2022-03-05 17:14:21 INFO ---------------------------------------------------
>>> bt_runner.run(SimpleSequence, '2')
2022-03-05 17:14:56 INFO creating SimpleSequence
2022-03-05 17:14:56 WARN SimpleSequence takes 2 argument(s), but 1 was/were provided
2022-03-05 17:14:56 INFO creating AddTwoNumbersActionWithFailures
2022-03-05 17:14:56 INFO finished SimpleSequence
2022-03-05 17:14:56 WARN ---------------------------------------------------
2022-03-05 17:14:56 WARN bt execution finished
2022-03-05 17:14:56 WARN status: NodeStatus.FAILURE
2022-03-05 17:14:56 WARN contingency-message: ONE_PARAM_MISSING
2022-03-05 17:14:56 WARN ---------------------------------------------------
>>> bt_runner.run(SimpleSequence, '')
2022-03-05 17:15:21 INFO creating SimpleSequence
2022-03-05 17:15:21 WARN SimpleSequence takes 2 argument(s), but 0 was/were provided
2022-03-05 17:15:21 INFO creating AddTwoNumbersActionWithFailures
2022-03-05 17:15:21 INFO finished SimpleSequence
2022-03-05 17:15:21 WARN ---------------------------------------------------
2022-03-05 17:15:21 WARN bt execution finished
2022-03-05 17:15:21 WARN status: NodeStatus.FAILURE
2022-03-05 17:15:21 WARN contingency-message: BOTH_PARAMS_MISSING
2022-03-05 17:15:21 WARN ---------------------------------------------------
>>> bt_runner.run(SimpleSequence, '6 8')
2022-03-05 17:15:43 INFO creating SimpleSequence
2022-03-05 17:15:43 INFO creating AddTwoNumbersActionWithFailures
AddTwoNumbersActionWithFailures: calculating: 6 + 8 = 14 -> RESULT_TOO_LARGE
2022-03-05 17:15:43 INFO finished SimpleSequence
2022-03-05 17:15:43 WARN ---------------------------------------------------
2022-03-05 17:15:43 WARN bt execution finished
2022-03-05 17:15:43 WARN status: NodeStatus.FAILURE
2022-03-05 17:15:43 WARN contingency-message: RESULT_TOO_LARGE
2022-03-05 17:15:43 WARN ---------------------------------------------------
The first execution shows the standard case where two input parameters are provided which have a sum
smaller than ten. As in this case the AddTwoNumbersActionWithFailures
node completes with
SUCCESS
the PrintNumberAction
is also executed. The subsequent three executions have in common,
that the AddTwoNumbersActionWithFailures
completes with FAILURE
, and thus the sequence directly completes
also with FAILURE
- without executing the PrintNumberAction
node. The only difference is that
the contingency-message differs in the three cases.
Create a sequence with contingency-handling
In this example the SimpleSequence
node is extended by two contingency-handlers. The new sequence
is called ContingencySequence
.
Add the following content to sequence_with_contingencies.py
.
Or use the provided file: sequence_with_contingencies.py
1class ContingencySequence(SequenceNode):
2 """The `ContingencySequence` example node.
3
4 The `ContingencySequence` extends the `Sequence` by showing how contingency
5 handlers can be registered. In case of the contingency `RESULT_TOO_LARGE` all
6 child nodes are removed and the two nodes `CreateRandomNumberAction` and
7 `PrintNumberAction` are added. For the two contingencies `ONE_PARAM_MISSING`
8 and `BOTH_PARAMS_MISSING` one contingency-handler using a wildcard `*_MISSING`
9 is registered. In this case the output parameter *?c* is set to 0 and the
10 current child is set to fixed. Setting a chiuld to fixed deletes the current
11 contingency message and sets the status to `FIXED`. As `FIXED` is handled
12 in the same way as `SUCCESS`, the sequence continues as everythig was normal.
13
14 Input Parameters
15 ----------------
16 ?a : int
17 The first value
18 ?b : int
19 The second value
20
21 Output Parameters
22 -----------------
23 ?c : int
24 The result
25
26 """
27
28 def __init__(self, bt_runner):
29 super().__init__(bt_runner, '?a ?b')
30
31 def on_init(self) -> None:
32 self.append_child(AddTwoNumbersActionWithFailures, '?a ?b => ?c')
33 self.append_child(PrintNumberAction, '?c')
34
35 self.register_contingency_handler(AddTwoNumbersActionWithFailures,
36 [NodeStatus.FAILURE],
37 'RESULT_TOO_LARGE',
38 self.fix_large_result)
39
40 self.register_contingency_handler(AddTwoNumbersActionWithFailures,
41 [NodeStatus.FAILURE],
42 # r'ONE_PARAM_MISSING|BOTH_PARAMS_MISSING',
43 # r'.*_MISSING',
44 r'.*_PARAM(S)?_MISSING',
45 self.fix_missing_input)
46
47 def fix_large_result(self) -> None:
48 print('fix_large_result')
49 self.remove_all_children()
50 self.append_child(CreateRandomNumberAction, '=> ?c')
51 self.append_child(PrintNumberAction, '?c')
52
53 def fix_missing_input(self) -> None:
54 print('fix_missing_input: set ?c = 0')
55 self._c = 0
56 self.fix_current_child()
57
58 def on_delete(self) -> None:
59 if len(self.get_contingency_history()) > 0:
60 entry: ContingencyHistoryEntry = self.get_contingency_history()[-1]
61 if(entry.contingency_message == 'ONE_PARAM_MISSING'
62 or entry.contingency_message == 'BOTH_PARAMS_MISSING'):
63 self.set_contingency_message('MISSING_PARAM_FIXED')
64 elif entry.contingency_message == 'RESULT_TOO_LARGE':
65 self.set_contingency_message('TOO_LARGE_RESULT_FIXED')
The code explained
In the on_init
function the two child nodes are added. Afterwards two contingency-handlers are registered. The
first one is attached to the AddTwoNumbersActionWithFailures
child and triggers in case the child node completes
with FAILURE
and with the contingency-message RESULT_TOO_LARGE
. In this case the fix_large_result
function is called.
self.register_contingency_handler(AddTwoNumbersActionWithFailures,
[NodeStatus.FAILURE],
'RESULT_TOO_LARGE',
self.fix_large_result)
The fix_large_result
function removes all children from the sequence and adds the two new child nodes
CreateRandomNumberAction
and PrintNumberAction
.
def fix_large_result(self) -> None:
print('fix_large_result')
self.remove_all_children()
self.append_child(CreateRandomNumberAction, '=> ?c')
self.append_child(PrintNumberAction, '?c')
The second contingency-handler is also attached to the AddTwoNumbersActionWithFailures
child and triggers
in case the child node completes with FAILURE
and the contingency-message matches the regular expression
r'.*_PARAM(S)?_MISSING'
. Thus, the fix_missing_input
function is triggered for both contingency-messages
the AddTwoNumbersActionWithFailures
node can provide. Alternatively the regular expression could be formulated,
for example, as follows: r'ONE_PARAM_MISSING|BOTH_PARAMS_MISSING'
or r'.*_MISSING'
.
self.register_contingency_handler(AddTwoNumbersActionWithFailures,
[NodeStatus.FAILURE],
# r'ONE_PARAM_MISSING|BOTH_PARAMS_MISSING',
# r'.*_MISSING',
r'.*_PARAM(S)?_MISSING',
self.fix_missing_input)
The fix_missing_input
function sets the output parameter _c
resp. ?c
to zero and the currently
executing child - which is the AddTwoNumbersActionWithFailures
- to FIXED
. As FIXED
is handled
in the same way as SUCCESS
the execution continues with the PrintNumberAction
with input parameter
?c set to zero.
def fix_missing_input(self) -> None:
print('fix_missing_input: set ?c = 0')
self._c = 0
self.fix_current_child()
The on_delete
callback is called right after all child nodes have finished their execution. In this example,
it is checked whether a contingency-handler was executed. If that was the case the contingency-message is set
accordingly to indicate that to the parent node. Thus, the parent node will be able to analyse the contingency-message,
as well as the contingency-history to figure out how the task was completed with SUCCESS.
def on_delete(self) -> None:
if len(self.get_contingency_history()) > 0:
entry: ContingencyHistoryEntry = self.get_contingency_history()[-1]
if(entry.contingency_message == 'ONE_PARAM_MISSING'
or entry.contingency_message == 'BOTH_PARAMS_MISSING'):
self.set_contingency_message('MISSING_PARAM_FIXED')
elif entry.contingency_message == 'RESULT_TOO_LARGE':
self.set_contingency_message('TOO_LARGE_RESULT_FIXED')
Run the example
Start the Python interpreter and run the ContingencySequence
node:
>>> import carebt
>>> from carebt import LogLevel
>>> from carebt.examples.sequence_with_contingencies import ContingencySequence
>>> bt_runner = carebt.BehaviorTreeRunner()
>>> bt_runner.get_logger().set_log_level(LogLevel.INFO)
>>> bt_runner.run(ContingencySequence, '6 4')
2022-03-05 17:16:26 INFO creating ContingencySequence
2022-03-05 17:16:26 INFO creating AddTwoNumbersActionWithFailures
AddTwoNumbersActionWithFailures: calculating: 6 + 4 = 10
2022-03-05 17:16:26 INFO creating PrintNumberAction
PrintNumberAction: number = 10
2022-03-05 17:16:26 INFO finished ContingencySequence
2022-03-05 17:16:26 INFO ---------------------------------------------------
2022-03-05 17:16:26 INFO bt execution finished
2022-03-05 17:16:26 INFO status: NodeStatus.SUCCESS
2022-03-05 17:16:26 INFO contingency-message:
2022-03-05 17:16:26 INFO ---------------------------------------------------
>>> bt_runner.run(ContingencySequence, '6')
2022-03-05 17:42:54 INFO creating ContingencySequence
2022-03-05 17:42:54 WARN ContingencySequence takes 2 argument(s), but 1 was/were provided
2022-03-05 17:42:54 INFO creating AddTwoNumbersActionWithFailures
fix_missing_input: set ?c = 0
2022-03-05 17:42:54 INFO creating PrintNumberAction
PrintNumberAction: number = 0
2022-03-05 17:42:54 INFO finished ContingencySequence
2022-03-05 17:42:54 INFO ---------------------------------------------------
2022-03-05 17:42:54 INFO bt execution finished
2022-03-05 17:42:54 INFO status: NodeStatus.SUCCESS
2022-03-05 17:42:54 INFO contingency-message: MISSING_PARAM_FIXED
2022-03-05 17:42:54 INFO contingency-history: [0] AddTwoNumbersActionWithFailures
2022-03-05 17:42:54 INFO NodeStatus.FAILURE
2022-03-05 17:42:54 INFO ONE_PARAM_MISSING
2022-03-05 17:42:54 INFO fix_missing_input
2022-03-05 17:42:54 INFO ---------------------------------------------------
>>> bt_runner.run(ContingencySequence, '')
2022-03-05 17:43:23 INFO creating ContingencySequence
2022-03-05 17:43:23 WARN ContingencySequence takes 2 argument(s), but 0 was/were provided
2022-03-05 17:43:23 INFO creating AddTwoNumbersActionWithFailures
fix_missing_input: set ?c = 0
2022-03-05 17:43:23 INFO creating PrintNumberAction
PrintNumberAction: number = 0
2022-03-05 17:43:23 INFO finished ContingencySequence
2022-03-05 17:43:23 INFO ---------------------------------------------------
2022-03-05 17:43:23 INFO bt execution finished
2022-03-05 17:43:23 INFO status: NodeStatus.SUCCESS
2022-03-05 17:43:23 INFO contingency-message: MISSING_PARAM_FIXED
2022-03-05 17:43:23 INFO contingency-history: [0] AddTwoNumbersActionWithFailures
2022-03-05 17:43:23 INFO NodeStatus.FAILURE
2022-03-05 17:43:23 INFO BOTH_PARAMS_MISSING
2022-03-05 17:43:23 INFO fix_missing_input
2022-03-05 17:43:23 INFO ---------------------------------------------------
>>> bt_runner.run(ContingencySequence, '6 9')
2022-03-05 17:43:48 INFO creating ContingencySequence
2022-03-05 17:43:48 INFO creating AddTwoNumbersActionWithFailures
AddTwoNumbersActionWithFailures: calculating: 6 + 9 = 15 -> RESULT_TOO_LARGE
fix_large_result
2022-03-05 17:43:48 INFO creating CreateRandomNumberAction
CreateRandomNumberAction: number = 5
2022-03-05 17:43:48 INFO creating PrintNumberAction
PrintNumberAction: number = 5
2022-03-05 17:43:48 INFO finished ContingencySequence
2022-03-05 17:43:48 INFO ---------------------------------------------------
2022-03-05 17:43:48 INFO bt execution finished
2022-03-05 17:43:48 INFO status: NodeStatus.SUCCESS
2022-03-05 17:43:48 INFO contingency-message: TOO_LARGE_RESULT_FIXED
2022-03-05 17:43:48 INFO contingency-history: [0] AddTwoNumbersActionWithFailures
2022-03-05 17:43:48 INFO NodeStatus.FAILURE
2022-03-05 17:43:48 INFO RESULT_TOO_LARGE
2022-03-05 17:43:48 INFO fix_large_result
2022-03-05 17:43:48 INFO ---------------------------------------------------
Again, the first execution demonstrates the ‘good’ case where two input parameters with a sum smaller
than ten are provided. Thus, the AddTwoNumbersActionWithFailures
completes with SUCCESS
and the
PrintNumberAction
is executed. The next two execution show the cases where one or both input parameters
are missing. The fix_missing_input
contingency-handler-function is executed which sets ?c
to zero
and fixes the AddTwoNumbersActionWithFailures
. Thus, the sequence execution conntinues with
PrintNumberAction
. The next execution demonstrates the case when the sum of the two input parameters
is greater than ten. In this case the fix_large_result
contingency-handler-function is executed which
removes all child nodes and add two new ones. The last three runs show, how the execution of the contingency
handlers is documented by the contingency-history.