Behaviours
A Behaviour
is the smallest element in a
behaviour tree, i.e. it is the leaf. Behaviours are usually representative of
either a check (am I hungry?), or an action (buy some chocolate cookies).
Skeleton
Behaviours in py_trees are created by subclassing the
Behaviour
class. A skeleton example:
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3
4import py_trees
5import random
6
7
8class Foo(py_trees.behaviour.Behaviour):
9 def __init__(self, name):
10 """
11 Minimal one-time initialisation. A good rule of thumb is
12 to only include the initialisation relevant for being able
13 to insert this behaviour in a tree for offline rendering to
14 dot graphs.
15
16 Other one-time initialisation requirements should be met via
17 the setup() method.
18 """
19 super(Foo, self).__init__(name)
20
21 def setup(self):
22 """
23 When is this called?
24 This function should be either manually called by your program
25 to setup this behaviour alone, or more commonly, via
26 :meth:`~py_trees.behaviour.Behaviour.setup_with_descendants`
27 or :meth:`~py_trees.trees.BehaviourTree.setup`, both of which
28 will iterate over this behaviour, it's children (it's children's
29 children ...) calling :meth:`~py_trees.behaviour.Behaviour.setup`
30 on each in turn.
31
32 If you have vital initialisation necessary to the success
33 execution of your behaviour, put a guard in your
34 :meth:`~py_trees.behaviour.Behaviour.initialise` method
35 to protect against entry without having been setup.
36
37 What to do here?
38 Delayed one-time initialisation that would otherwise interfere
39 with offline rendering of this behaviour in a tree to dot graph
40 or validation of the behaviour's configuration.
41
42 Good examples include:
43
44 - Hardware or driver initialisation
45 - Middleware initialisation (e.g. ROS pubs/subs/services)
46 - A parallel checking for a valid policy configuration after
47 children have been added or removed
48 """
49 self.logger.debug(" %s [Foo::setup()]" % self.name)
50
51 def initialise(self):
52 """
53 When is this called?
54 The first time your behaviour is ticked and anytime the
55 status is not RUNNING thereafter.
56
57 What to do here?
58 Any initialisation you need before putting your behaviour
59 to work.
60 """
61 self.logger.debug(" %s [Foo::initialise()]" % self.name)
62
63 def update(self):
64 """
65 When is this called?
66 Every time your behaviour is ticked.
67
68 What to do here?
69 - Triggering, checking, monitoring. Anything...but do not block!
70 - Set a feedback message
71 - return a py_trees.common.Status.[RUNNING, SUCCESS, FAILURE]
72 """
73 self.logger.debug(" %s [Foo::update()]" % self.name)
74 ready_to_make_a_decision = random.choice([True, False])
75 decision = random.choice([True, False])
76 if not ready_to_make_a_decision:
77 return py_trees.common.Status.RUNNING
78 elif decision:
79 self.feedback_message = "We are not bar!"
80 return py_trees.common.Status.SUCCESS
81 else:
82 self.feedback_message = "Uh oh"
83 return py_trees.common.Status.FAILURE
84
85 def terminate(self, new_status):
86 """
87 When is this called?
88 Whenever your behaviour switches to a non-running state.
89 - SUCCESS || FAILURE : your behaviour's work cycle has finished
90 - INVALID : a higher priority branch has interrupted, or shutting down
91 """
92 self.logger.debug(" %s [Foo::terminate().terminate()][%s->%s]" % (self.name, self.status, new_status))
Lifecycle
Getting a feel for how this works in action can be seen by running the py-trees-demo-behaviour-lifecycle program (click the link for more detail and access to the sources):
Important points to focus on:
The
initialise()
method kicks in only when the behaviour is not already runningThe parent
tick()
method is responsible for determining when to callinitialise()
,stop()
andterminate()
methods.The parent
tick()
method always calls update()The
update()
method is responsible for deciding the behaviour Status.
Initialisation
With no less than three methods used for initialisation, it can be difficult to identify where your initialisation code needs to lurk.
Note
__init__
should instantiate the behaviour sufficiently for offline dot graph generation
Later we’ll see how we can render trees of behaviours in dot graphs. For now, it is sufficient to understand that you need to keep this minimal enough so that you can generate dot graphs for your trees from something like a CI server (e.g. Jenkins). This is a very useful thing to be able to do.
No hardware connections that may not be there, e.g. usb lidars
No middleware connections to other software that may not be there, e.g. ROS pubs/subs/services
No need to fire up other needlessly heavy resources, e.g. heavy threads in the background
Note
setup
handles all other one-time initialisations of resources that are required for execution
Essentially, all the things that the constructor doesn’t handle - hardware connections, middleware and other heavy resources.
Note
initialise
configures and resets the behaviour ready for (repeated) execution
Initialisation here is about getting things ready for immediate execution of a task. Some examples:
Initialising/resetting/clearing variables
Starting timers
Just-in-time discovery and establishment of middleware connections
Sending a goal to start a controller running elsewhere on the system
…
Status
The most important part of a behaviour is the determination of the behaviour’s status
in the update()
method. The status gets used to affect which direction
of travel is subsequently pursued through the remainder of a behaviour tree. We haven’t gotten
to trees yet, but it is this which drives the decision making in a behaviour tree.
- class py_trees.common.Status(value)[source]
An enumerator representing the status of a behaviour.
- FAILURE = 'FAILURE'
Behaviour check has failed, or execution of its action finished with a failed result.
- INVALID = 'INVALID'
Behaviour is uninitialised and/or in an inactive state, i.e. not currently being ticked.
- RUNNING = 'RUNNING'
Behaviour is in the middle of executing some action, result still pending.
- SUCCESS = 'SUCCESS'
Behaviour check has passed, or execution of its action has finished with a successful result.
The update()
method must return one of RUNNING
. SUCCESS
or FAILURE
. A
status of INVALID
is the initial default and ordinarily automatically set by other
mechansims (e.g. when a higher priority behaviour cancels the currently selected one).
Feedback Message
1 description=description(),
2 epilog=epilog(),
3 formatter_class=argparse.RawDescriptionHelpFormatter,
4 )
A behaviour has a naturally built in feedback message that can be
cleared in the initialise()
or terminate()
methods and updated
in the update()
method.
Tip
Alter a feedback message when significant events occur.
The feedback message is designed to assist in notifying humans when a significant event happens or for deciding when to log the state of a tree. If you notify or log every tick, then you end up with a lot of noise sorting through an abundance of data in which nothing much is happening to find the one point where something significant occurred that led to surprising or catastrophic behaviour.
Setting the feedback message is usually important when something
significant happens in the RUNNING
state or to provide information
associated with the result (e.g. failure reason).
Example - a behaviour responsible for planning motions of a
character is in the RUNNING
state for a long period of time.
Avoid updating it with a feedback message at every tick with updated plan
details. Instead, update the message whenever a significant change
occurs - e.g. when the previous plan is re-planned or pre-empted.
Loggers
These are used throughout the demo programs. They are not intended to be for anything heavier than debugging simple examples. This kind of logging tends to get rather heavy and requires a lot of filtering to find the points of change that you are interested in (see comments about the feedback messages above).
Complex Example
The py-trees-demo-action-behaviour program demonstrates
a more complicated behaviour that illustrates a few
concepts discussed above, but not present in the very simple lifecycle
Counter
behaviour.
Mocks an external process and connects to it in the
setup
methodKickstarts new goals with the external process in the
initialise
methodMonitors the ongoing goal status in the
update
methodDetermines
RUNNING
/SUCCESS
pending feedback from the external process
Note
A behaviour’s update()
method never blocks, at most it just monitors the
progress and holds up any decision making required by a tree that is ticking the
behaviour by setting it’s status to RUNNING
. At the risk of being confusing, this
is what is generally referred to as a blocking behaviour.