Writing a node with parameters

Overview

This tutorial shows how to implement nodes in careBT which use input and/or output parameters. In this example an ActionNode, called AddTwoNumbersAction, is implemented which receives two numbers (?x, ?y) as input parameters, adds these two numbers and binds the result to an output parameter (?z). This is, of course, a simplified example, as adding two numbers is typically not done by an custom action node. But it demonstrates how parameters are working in careBT, without caring about the scenario and how “realistic” it is.

The signature (input/output parameters) of a node is defined by a string provided to the constructor of the careBT node the custom node inherits from and works for all careBT nodes in the same way. The syntax is <list of input parameters> => <list of output parameters> with parameter names starting with ?. Each parameter is then available in the custom node class with the name _<variable-name>.

Create the AddTwoNumbersAction ActionNode

Create a file named action_with_params.py with following content. Or use the provided file: action_with_params.py

 1from carebt import ActionNode
 2from carebt import NodeStatus
 3
 4
 5class AddTwoNumbersAction(ActionNode):
 6    """The `AddTwoNumbersAction` demonstrates a careBT `ActionNode`.
 7
 8    The `AddTwoNumbersAction` demonstrates a careBT `ActionNode` with two
 9    input parameters and one output parameter. It takes the two inputs,
10    adds them and returns the result.
11
12    Input Parameters
13    ----------------
14    ?x : int, default = 0
15        The first value
16    ?y : int, default = 0
17        The second value
18
19    Output Parameters
20    -----------------
21    ?z : int
22        The sum of ?x and ?y
23
24    """
25
26    def __init__(self, bt_runner):
27        super().__init__(bt_runner, '?x ?y => ?z')
28
29    def on_init(self) -> None:
30        if(self._x is None):
31            self._x = 0
32        if(self._y is None):
33            self._y = 0
34
35    def on_tick(self) -> None:
36        self._z = self._x + self._y
37        print(f'AddTwoNumbersAction: calculating: {self._x} + {self._y} = {self._z}')
38        self.set_status(NodeStatus.SUCCESS)

The code explained

The first two statements are the includes for the ActionNode and the NodeStatus.

from carebt import ActionNode
from carebt import NodeStatus

The AddTwoNumbersAction node is implemented as a Python class which inherits from ActionNode.

class AddTwoNumbersAction(ActionNode):

The class definition is followed by the Docstring documentation of the node, which also documents the interface (input/output parameters).

    """The `AddTwoNumbersAction` demonstrates a careBT `ActionNode`.

    The `AddTwoNumbersAction` demonstrates a careBT `ActionNode` with two
    input parameters and one output parameter. It takes the two inputs,
    adds them and returns the result.

    Input Parameters
    ----------------
    ?x : int, default = 0
        The first value
    ?y : int, default = 0
        The second value

    Output Parameters
    -----------------
    ?z : int
        The sum of ?x and ?y

    """

The constructor (__init__) of the AddTwoNumbersAction node needs to call the constructor (super().__init__) of the ActionNode and passes the bt_runner and the signature as arguments. The input parameters are ?x and ?y, and the output parameter is ?z. These parameters are then available inside the node as member variables, called: _x, _y and _z.

    def __init__(self, bt_runner):
        super().__init__(bt_runner, '?x ?y => ?z')

The on_init function is called rigth after the node was created. It is the place to put the code which should be executed once, after the node was created. In this example it is implemented that the two input parameters are checked if values are bound to them during creation. If the variables are not bound (... is None), the value is set zero.

    def on_init(self) -> None:
        if(self._x is None):
            self._x = 0
        if(self._y is None):
            self._y = 0

In the on_tick function the two inputs are added and assiged to the output. Furthermore, this calculation is printed on standard output and the node status is set to SUCCESS. Thus, the AddTwoNumbersAction node is not ticked again.

    def on_tick(self) -> None:
        self._z = self._x + self._y
        print(f'AddTwoNumbersAction: calculating: {self._x} + {self._y} = {self._z}')
        self.set_status(NodeStatus.SUCCESS)

Run the example

Start the Python interpreter and run the AddTwoNumbersAction node:

>>> import carebt
>>> from carebt.examples.action_with_params import AddTwoNumbersAction
>>> bt_runner = carebt.BehaviorTreeRunner()
>>> bt_runner.run(AddTwoNumbersAction, '2 3 => ?sum')
AddTwoNumbersAction: calculating: 2 + 3 = 5
>>> bt_runner.run(AddTwoNumbersAction, '4 => ?sum')
2021-11-12 22:13:07 WARN AddTwoNumbersAction takes 2 argument(s), but 1 was/were provided
AddTwoNumbersAction: calculating: 4 + 0 = 4
>>> bt_runner.run(AddTwoNumbersAction, '=> ?sum')
2021-11-12 22:13:48 WARN AddTwoNumbersAction takes 2 argument(s), but 0 was/were provided
AddTwoNumbersAction: calculating: 0 + 0 = 0

Above the AddTwoNumbersAction node is executed three times. The first execution is the standard case, where two input parameters are provided as expected. In the second and third execution one or both input parameters are missing. This is announced as a CareBT-warning and the default values are used.