Module API

py_trees

This is the top-level namespace of the py_trees package.

py_trees.behaviour

The core behaviour template. All behaviours, standalone and composite, inherit from this class.

class py_trees.behaviour.Behaviour(name=<Name.AUTO_GENERATED: 'AUTO_GENERATED'>)[source]

Bases: object

Defines the basic properties and methods required of a node in a behaviour tree. When implementing your own behaviour, subclass this class.

Parameters:

name (Union[str, Name]) – the behaviour name, defaults to auto-generating from the class name

Raises:

TypeError – if the provided name is not a string

Variables:
  • id (uuid.UUID) – automagically generated unique identifier for the behaviour
  • name (str) – the behaviour name
  • blackboards (typing.List[py_trees.blackboard.Client]) – collection of attached blackboard clients
  • status (Status) – the behaviour status (INVALID, RUNNING, FAILURE, SUCCESS)
  • parent (Behaviour) – a Composite instance if nested in a tree, otherwise None
  • children ([Behaviour]) – empty for regular behaviours, populated for composites
  • logger (logging.Logger) – a simple logging mechanism
  • feedback_message (str) – improve debugging with a simple message
  • blackbox_level (BlackBoxLevel) – a helper variable for dot graphs and runtime gui’s to collapse/explode entire subtrees dependent upon the blackbox level.
attach_blackboard_client(name=None, namespace=None)[source]

Create and attach a blackboard to this behaviour.

Parameters:
  • name (Optional[str]) – human-readable (not necessarily unique) name for the client
  • namespace (Optional[str]) – sandbox the client to variables behind this namespace
Return type:

Client

Returns:

a handle to the attached blackboard client

has_parent_with_instance_type(instance_type)[source]

Moves up through this behaviour’s parents looking for a behaviour with the same instance type as that specified.

Parameters:instance_type (str) – instance type of the parent to match
Returns:whether a parent was found or not
Return type:bool
has_parent_with_name(name)[source]

Searches through this behaviour’s parents, and their parents, looking for a behaviour with the same name as that specified.

Parameters:name (str) – name of the parent to match, can be a regular expression
Returns:whether a parent was found or not
Return type:bool
initialise()[source]

Note

User Customisable Callback

Subclasses may override this method to perform any necessary initialising/clearing/resetting of variables when when preparing to enter this behaviour if it was not previously RUNNING. i.e. Expect this to trigger more than once!

iterate(direct_descendants=False)[source]

Generator that provides iteration over this behaviour and all its children. To traverse the entire tree:

for node in my_behaviour.iterate():
    print("Name: {0}".format(node.name))
Parameters:direct_descendants (bool) – only yield children one step away from this behaviour.
Yields:Behaviour – one of it’s children
setup(**kwargs)[source]

Note

User Customisable Callback

Subclasses may override this method for any one-off delayed construction & validation that is necessary prior to ticking the tree. Such construction is best done here rather than in __init__ so that trees can be instantiated on the fly for easy rendering to dot graphs without imposing runtime requirements (e.g. establishing a middleware connection to a sensor or a driver to a serial port).

Equally as important, executing methods which validate the configuration of behaviours will increase confidence that your tree will successfully tick without logical software errors before actually ticking. This is useful both before a tree’s first tick and immediately after any modifications to a tree has been made between ticks.

Tip

Faults are notified to the user of the behaviour via exceptions. Choice of exception to use is left to the user.

Warning

The kwargs argument is for distributing objects at runtime to behaviours before ticking. For example, a simulator instance with which behaviours can interact with the simulator’s python api, a ros2 node for setting up communications. Use sparingly, as this is not proof against keyword conflicts amongst disparate libraries of behaviours.

Parameters:**kwargs (dict) – distribute arguments to this behaviour and in turn, all of it’s children
Raises:Exception – if this behaviour has a fault in construction or configuration
setup_with_descendants()[source]

Iterates over this child, it’s children (it’s children’s children, …) calling the user defined setup() on each in turn.

shutdown()[source]

Note

User Customisable Callback

Subclasses may override this method for any custom destruction of infrastructure usually brought into being in setup().

Raises:Exception – of whatever flavour the child raises when errors occur on destruction
stop(new_status=<Status.INVALID: 'INVALID'>)[source]
Parameters:new_status (Status) – the behaviour is transitioning to this new status

This calls the user defined terminate() method and also resets the generator. It will finally set the new status once the user’s terminate() function has been called.

Warning

Override this method only in exceptional circumstances, prefer overriding terminate() instead.

terminate(new_status)[source]

Note

User Customisable Callback

Subclasses may override this method to clean up. It will be triggered when a behaviour either finishes execution (switching from RUNNING to FAILURE || SUCCESS) or it got interrupted by a higher priority branch (switching to INVALID). Remember that the initialise() method will handle resetting of variables before re-entry, so this method is about disabling resources until this behaviour’s next tick. This could be a indeterminably long time. e.g.

  • cancel an external action that got started
  • shut down any tempoarary communication handles
Parameters:new_status (Status) – the behaviour is transitioning to this new status

Warning

Do not set self.status = new_status here, that is automatically handled by the stop() method. Use the argument purely for introspection purposes (e.g. comparing the current state in self.status with the state it will transition to in new_status.

tick()[source]

This function is a generator that can be used by an iterator on an entire behaviour tree. It handles the logic for deciding when to call the user’s initialise() and terminate() methods as well as making the actual call to the user’s update() method that determines the behaviour’s new status once the tick has finished. Once done, it will then yield itself (generator mechanism) so that it can be used as part of an iterator for the entire tree.

for node in my_behaviour.tick():
    print("Do something")

Note

This is a generator function, you must use this with yield. If you need a direct call, prefer tick_once() instead.

Yields:Behaviour – a reference to itself

Warning

Override this method only in exceptional circumstances, prefer overriding update() instead.

tick_once()[source]

A direct means of calling tick on this object without using the generator mechanism.

tip()[source]

Get the tip of this behaviour’s subtree (if it has one) after it’s last tick. This corresponds to the the deepest node that was running before the subtree traversal reversed direction and headed back to this node.

Returns:child behaviour, itself or None if its status is INVALID
Return type:Behaviour or None
update()[source]

Note

User Customisable Callback

Returns:the behaviour’s new status Status
Return type:Status

Subclasses may override this method to perform any logic required to arrive at a decision on the behaviour’s new status. It is the primary worker function called on by the tick() mechanism.

Tip

This method should be almost instantaneous and non-blocking

visit(visitor)[source]

This is functionality that enables external introspection into the behaviour. It gets used by the tree manager classes to collect information as ticking traverses a tree.

Parameters:visitor (object) – the visiting class, must have a run(Behaviour) method.

py_trees.behaviours

A library of fundamental behaviours for use.

class py_trees.behaviours.BlackboardToStatus(variable_name, name=<Name.AUTO_GENERATED: 'AUTO_GENERATED'>)[source]

Bases: py_trees.behaviour.Behaviour

This behaviour reverse engineers the StatusToBlackboard decorator. Used in conjuction with that decorator, this behaviour can be used to reflect the status of a decision elsewhere in the tree.

Note

A word of caution. The consequences of a behaviour’s status should be discernable upon inspection of the tree graph. If using StatusToBlackboard and BlackboardToStatus to reflect a behaviour’s status across a tree, this is no longer true. The graph of the tree communicates the local consequences, but not the reflected consequences at the point BlackboardToStatus is used. A recommendation, use this class only where other options are infeasible or impractical.

Parameters:
  • variable_name (str) – name of the variable look for, may be nested, e.g. battery.percentage
  • name (Union[str, Name]) – name of the behaviour
Raises:
update()[source]

Check for existence.

Return type:Status
Returns:SUCCESS if key found, FAILURE otherwise.
class py_trees.behaviours.CheckBlackboardVariableExists(variable_name, name=<Name.AUTO_GENERATED: 'AUTO_GENERATED'>)[source]

Bases: py_trees.behaviour.Behaviour

Check the blackboard to verify if a specific variable (key-value pair) exists. This is non-blocking, so will always tick with status FAILURE SUCCESS.

See also

WaitForBlackboardVariable for the blocking counterpart to this behaviour.

Parameters:
  • variable_name (str) – name of the variable look for, may be nested, e.g. battery.percentage
  • name (Union[str, Name]) – name of the behaviour
update()[source]

Check for existence.

Return type:Status
Returns:SUCCESS if key found, FAILURE otherwise.
class py_trees.behaviours.CheckBlackboardVariableValue(check, name=<Name.AUTO_GENERATED: 'AUTO_GENERATED'>)[source]

Bases: py_trees.behaviour.Behaviour

Inspect a blackboard variable and if it exists, check that it meets the specified criteria (given by operation type and expected value). This is non-blocking, so it will always tick with SUCCESS or FAILURE.

Parameters:
  • check (ComparisonExpression) – a comparison expression to check against
  • name (Union[str, Name]) – name of the behaviour

Note

If the variable does not yet exist on the blackboard, the behaviour will return with status FAILURE.

Tip

The python operator module includes many useful comparison operations.

update()[source]

Check for existence, or the appropriate match on the expected value.

Returns:FAILURE if not matched, SUCCESS otherwise.
Return type:Status
class py_trees.behaviours.CheckBlackboardVariableValues(checks, operator, name=<Name.AUTO_GENERATED: 'AUTO_GENERATED'>, namespace=None)[source]

Bases: py_trees.behaviour.Behaviour

Apply a logical operation across a set of blackboard variable checks. This is non-blocking, so will always tick with status FAILURE or SUCCESS.

Parameters:
  • checks (List[ComparisonExpression]) – a list of comparison checks to apply to blackboard variables
  • logical_operator – a logical check to apply across the results of the blackboard variable checks
  • name (Union[str, Name]) – name of the behaviour
  • namespace (Optional[str]) – optionally store results of the checks (boolean) under this namespace

Tip

The python operator module includes many useful logical operators, e.g. operator.xor.

Raises:ValueError if less than two variable checks are specified (insufficient for logical operations)
update()[source]

Applies comparison checks on each variable and a logical check across the complete set of variables.

Return type:Status
Returns:FAILURE if key retrieval or logical checks failed, SUCCESS otherwise.
class py_trees.behaviours.Count(name='Count', fail_until=3, running_until=5, success_until=6, reset=True)[source]

Bases: py_trees.behaviour.Behaviour

A counting behaviour that updates its status at each tick depending on the value of the counter. The status will move through the states in order - FAILURE, RUNNING, SUCCESS.

This behaviour is useful for simple testing and demo scenarios.

Parameters:
  • name (str) – name of the behaviour
  • fail_until (int) – set status to FAILURE until the counter reaches this value
  • running_until (int) – set status to RUNNING until the counter reaches this value
  • success_until (int) – set status to SUCCESS until the counter reaches this value
  • reset (bool) – whenever invalidated (usually by a sequence reinitialising, or higher priority interrupting)
Variables:

count (int) – a simple counter which increments every tick

terminate(new_status)[source]

Note

User Customisable Callback

Subclasses may override this method to clean up. It will be triggered when a behaviour either finishes execution (switching from RUNNING to FAILURE || SUCCESS) or it got interrupted by a higher priority branch (switching to INVALID). Remember that the initialise() method will handle resetting of variables before re-entry, so this method is about disabling resources until this behaviour’s next tick. This could be a indeterminably long time. e.g.

  • cancel an external action that got started
  • shut down any tempoarary communication handles
Parameters:new_status (Status) – the behaviour is transitioning to this new status

Warning

Do not set self.status = new_status here, that is automatically handled by the stop() method. Use the argument purely for introspection purposes (e.g. comparing the current state in self.status with the state it will transition to in new_status.

update()[source]

Note

User Customisable Callback

Returns:the behaviour’s new status Status
Return type:Status

Subclasses may override this method to perform any logic required to arrive at a decision on the behaviour’s new status. It is the primary worker function called on by the tick() mechanism.

Tip

This method should be almost instantaneous and non-blocking

class py_trees.behaviours.Dummy(name='Dummy')

Bases: py_trees.behaviour.Behaviour

class py_trees.behaviours.Failure(name='Failure')

Bases: py_trees.behaviour.Behaviour

class py_trees.behaviours.Periodic(name, n)[source]

Bases: py_trees.behaviour.Behaviour

Simply periodically rotates it’s status over the RUNNING, SUCCESS, FAILURE states. That is, RUNNING for N ticks, SUCCESS for N ticks, FAILURE for N ticks…

Parameters:
  • name (str) – name of the behaviour
  • n (int) – period value (in ticks)

Note

It does not reset the count when initialising.

update()[source]

Note

User Customisable Callback

Returns:the behaviour’s new status Status
Return type:Status

Subclasses may override this method to perform any logic required to arrive at a decision on the behaviour’s new status. It is the primary worker function called on by the tick() mechanism.

Tip

This method should be almost instantaneous and non-blocking

class py_trees.behaviours.Running(name='Running')

Bases: py_trees.behaviour.Behaviour

class py_trees.behaviours.SetBlackboardVariable(variable_name, variable_value, overwrite=True, name=<Name.AUTO_GENERATED: 'AUTO_GENERATED'>)[source]

Bases: py_trees.behaviour.Behaviour

Set the specified variable on the blackboard.

Parameters:
  • variable_name (str) – name of the variable to set, may be nested, e.g. battery.percentage
  • variable_value (Union[Any, Callable[[], Any]]) – value of the variable to set
  • overwrite (bool) – when False, do not set the variable if it already exists
  • name (Union[str, Name]) – name of the behaviour
update()[source]

Always return success.

Return type:Status
Returns:FAILURE if no overwrite requested and the variable exists, SUCCESS otherwise
class py_trees.behaviours.StatusSequence(name, sequence, eventually)[source]

Bases: py_trees.behaviour.Behaviour

Cycle through the specified sequence of states.

Parameters:
  • name (str) – name of the behaviour
  • sequence (List[Status]) – list of status values to cycle through
  • eventually (Optional[Status]) – status to use eventually, None to re-cycle the sequence
update()[source]

Note

User Customisable Callback

Returns:the behaviour’s new status Status
Return type:Status

Subclasses may override this method to perform any logic required to arrive at a decision on the behaviour’s new status. It is the primary worker function called on by the tick() mechanism.

Tip

This method should be almost instantaneous and non-blocking

class py_trees.behaviours.Success(name='Success')

Bases: py_trees.behaviour.Behaviour

class py_trees.behaviours.SuccessEveryN(name, n)[source]

Bases: py_trees.behaviour.Behaviour

This behaviour updates it’s status with SUCCESS once every N ticks, FAILURE otherwise.

Parameters:
  • name (str) – name of the behaviour
  • n (int) – trigger success on every n’th tick

Tip

Use with decorators to change the status value as desired, e.g. py_trees.decorators.FailureIsRunning()

update()[source]

Note

User Customisable Callback

Returns:the behaviour’s new status Status
Return type:Status

Subclasses may override this method to perform any logic required to arrive at a decision on the behaviour’s new status. It is the primary worker function called on by the tick() mechanism.

Tip

This method should be almost instantaneous and non-blocking

class py_trees.behaviours.TickCounter(duration, name=<Name.AUTO_GENERATED: 'AUTO_GENERATED'>, completion_status=<Status.SUCCESS: 'SUCCESS'>)[source]

Bases: py_trees.behaviour.Behaviour

A useful utility behaviour for demos and tests. Simply ticks with RUNNING for the specified number of ticks before returning the requested completion status (SUCCESS or FAILURE).

This behaviour will reset the tick counter when initialising.

Parameters:
  • name – name of the behaviour
  • duration (int) – number of ticks to run
  • completion_status (Status) – status to switch to once the counter has expired
initialise()[source]

Reset the tick counter.

update()[source]

Increment the tick counter and return the appropriate status for this behaviour based on the tick count.

Returns
RUNNING while not expired, the given completion status otherwise
class py_trees.behaviours.UnsetBlackboardVariable(key, name=<Name.AUTO_GENERATED: 'AUTO_GENERATED'>)[source]

Bases: py_trees.behaviour.Behaviour

Unset the specified variable (key-value pair) from the blackboard.

This always returns SUCCESS regardless of whether the variable was already present or not.

Parameters:
  • key (str) – unset this key-value pair
  • name (Union[str, Name]) – name of the behaviour
update()[source]

Unset and always return success.

Return type:Status
Returns:SUCCESS
class py_trees.behaviours.WaitForBlackboardVariable(variable_name, name=<Name.AUTO_GENERATED: 'AUTO_GENERATED'>)[source]

Bases: py_trees.behaviours.CheckBlackboardVariableExists

Wait for the blackboard variable to become available on the blackboard. This is blocking, so it will tick with status SUCCESS if the variable is found, and RUNNING otherwise.

See also

CheckBlackboardVariableExists for the non-blocking counterpart to this behaviour.

Parameters:
  • variable_name (str) – name of the variable to wait for, may be nested, e.g. battery.percentage
  • name (Union[str, Name]) – name of the behaviour
update()[source]

Check for existence, wait otherwise.

Return type:Status
Returns:SUCCESS if key found, RUNNING otherwise.
class py_trees.behaviours.WaitForBlackboardVariableValue(check, name=<Name.AUTO_GENERATED: 'AUTO_GENERATED'>)[source]

Bases: py_trees.behaviours.CheckBlackboardVariableValue

Inspect a blackboard variable and if it exists, check that it meets the specified criteria (given by operation type and expected value). This is blocking, so it will always tick with SUCCESS or RUNNING.

See also

CheckBlackboardVariableValue for the non-blocking counterpart to this behaviour.

Note

If the variable does not yet exist on the blackboard, the behaviour will return with status RUNNING.

Parameters:
  • check (ComparisonExpression) – a comparison expression to check against
  • name (Union[str, Name]) – name of the behaviour
update()[source]

Check for existence, or the appropriate match on the expected value.

Returns:FAILURE if not matched, SUCCESS otherwise.
Return type:Status

py_trees.blackboard

Blackboards are not a necessary component of behaviour tree implementations, but are nonetheless, a fairly common mechanism for sharing data between behaviours in the tree. See, for example, the design notes for blackboards in Unreal Engine.

_images/blackboard.jpg

Implementations vary widely depending on the needs of the framework using them. The simplest implementations take the form of a key-value store with global access, while more rigorous implementations scope access or form a secondary graph overlaying the tree connecting data ports between behaviours.

The ‘Zen of PyTrees’ is to enable rapid development, yet be rich enough so that all of the magic is exposed for debugging purposes. The first implementation of a blackboard was merely a global key-value store with an api that lent itself to ease of use, but did not expose the data sharing between behaviours which meant any tooling used to introspect or visualise the tree, only told half the story.

The current implementation adopts a strategy similar to that of a filesystem. Each client (subsequently behaviour) registers itself for read/write access to keys on the blackboard. This is less to do with permissions and more to do with tracking users of keys on the blackboard - extremely helpful with debugging.

The alternative approach of layering a secondary data graph with parameter and input-output ports on each behaviour was discarded as being too heavy for the zen requirements of py_trees. This is in part due to the wiring costs, but also due to complexity arising from a tree’s partial graph execution (a feature which makes trees different from most computational graph frameworks) and not to regress on py_trees’ capability to dynamically insert and prune subtrees on the fly.

A high-level list of existing / planned features:

  • [+] Centralised key-value store
  • [+] Client connections with namespaced read/write access to the store
  • [+] Integration with behaviours for key-behaviour associations (debugging)
  • [+] Activity stream that logs read/write operations by clients
  • [+] Exclusive locks for writing
  • [+] Framework for key remappings
class py_trees.blackboard.ActivityItem(key, client_name, client_id, activity_type, previous_value=None, current_value=None)[source]

Bases: object

Recorded data pertaining to activity on the blackboard.

Parameters:
  • key – name of the variable on the blackboard
  • client_name (str) – convenient name of the client performing the operation
  • client_id (UUID) – unique id of the client performing the operation
  • activity_type (str) – type of activity
  • previous_value (Optional[Any]) – of the given key (None if this field is not relevant)
  • current_value (Optional[Any]) – current value for the given key (None if this field is not relevant)
__init__(key, client_name, client_id, activity_type, previous_value=None, current_value=None)[source]

Initialize self. See help(type(self)) for accurate signature.

__weakref__

list of weak references to the object (if defined)

class py_trees.blackboard.ActivityStream(maximum_size=500)[source]

Bases: object

Storage container with convenience methods for manipulating the stored activity stream.

Variables:
  • (typing.List[ActivityItem] (data) – list of activity items, earliest first
  • maximum_size (int) – pop items if this size is exceeded
__init__(maximum_size=500)[source]

Initialise the stream with a maximum storage limit.

Parameters:maximum_size (int) – pop items from the stream if this size is exceeded
__weakref__

list of weak references to the object (if defined)

clear()[source]

Delete all activities from the stream.

push(activity_item)[source]

Push the next activity item to the stream.

Parameters:activity_item (ActivityItem) – new item to append to the stream
class py_trees.blackboard.ActivityType[source]

Bases: enum.Enum

An enumerator representing the operation on a blackboard variable

ACCESSED = 'ACCESSED'

Key accessed, either for reading, or modification of the value’s internal attributes (e.g. foo.bar).

ACCESS_DENIED = 'ACCESS_DENIED'

Client did not have access to read/write a key.

INITIALISED = 'INITIALISED'

Initialised a key-value pair on the blackboard

NO_KEY = 'NO_KEY'

Tried to access a key that does not yet exist on the blackboard.

NO_OVERWRITE = 'NO_OVERWRITE'

Tried to write but variable already exists and a no-overwrite request was respected.

READ = 'READ'

Read from the blackboard

UNSET = 'UNSET'

Key was removed from the blackboard

WRITE = 'WRITE'

Wrote to the blackboard.

class py_trees.blackboard.Blackboard[source]

Bases: object

Centralised key-value store for sharing data between behaviours. This class is a coat-hanger for the centralised data store, metadata for it’s administration and static methods for interacting with it.

This api is intended for authors of debugging and introspection tools on the blackboard. Users should make use of the Client.

Variables:
__weakref__

list of weak references to the object (if defined)

static absolute_name(namespace, key)[source]

Generate the fully qualified key name from namespace and name arguments.

Examples

'/' + 'foo'  = '/foo'
'/' + '/foo' = '/foo'
'/foo' + 'bar' = '/foo/bar'
'/foo/' + 'bar' = '/foo/bar'
'/foo' + '/foo/bar' = '/foo/bar'
'/foo' + '/bar' = '/bar'
'/foo' + 'foo/bar' = '/foo/foo/bar'
Parameters:
  • namespace (str) – namespace the key should be embedded in
  • key (str) – key name (relative or absolute)
Return type:

str

Returns:

the absolute name

Warning

To expedite the method call (it’s used with high frequency in blackboard key lookups), no checks are made to ensure the namespace argument leads with a “/”. Nor does it check that a name in absolute form is actually embedded in the specified namespace, it just returns the given (absolute) name directly.

static clear()[source]

Completely clear all key, value and client information from the blackboard. Also deletes the activity stream.

static disable_activity_stream()[source]

Disable logging of activities on the blackboard

static enable_activity_stream(maximum_size=500)[source]

Enable logging of activities on the blackboard.

Parameters:maximum_size (int) – pop items from the stream if this size is exceeded
Raises:RuntimeError if the activity stream is already enabled
static exists(name)[source]

Check if the specified variable exists on the blackboard.

Parameters:name (str) – name of the variable, can be nested, e.g. battery.percentage
Raises:AttributeError – if the client does not have read access to the variable
Return type:bool
static get(variable_name)[source]

Extract the value associated with the given a variable name, can be nested, e.g. battery.percentage. This differs from the client get method in that it doesn’t pass through the client access checks. To be used for utility tooling (e.g. display methods) and not by users directly.

Parameters:variable_name (str) – of the variable to get, can be nested, e.g. battery.percentage
Raises:KeyError – if the variable or it’s nested attributes do not yet exist on the blackboard
Return type:Any
Returns:The stored value for the given variable
static key(variable_name)[source]

Extract the key for an arbitrary blackboard variable, keeping in mind that blackboard variable names can be pointing to a nested attribute within a key.

Example: ‘/foo/bar.woohoo -> /foo/bar’.

Parameters:variable_name (str) – blackboard variable name - can be nested, e.g. battery.percentage
Return type:str
Returns:name of the underlying key
static key_with_attributes(variable_name)[source]

Extract the key for an arbitrary blackboard variable, keeping in mind that blackboard variable names can be pointing to a nested attribute within a key,

Example: ‘/foo/bar.woohoo -> (/foo/bar’. ‘woohoo’)

Parameters:variable_name (str) – blackboard variable name - can be nested, e.g. battery.percentage
Return type:Tuple[str, str]
Returns:a tuple consisting of the key and it’s attributes (in string form)
static keys()[source]

Get the set of blackboard keys.

Return type:Set[str]
Returns:the complete set of keys registered by clients
static keys_filtered_by_clients(client_ids)[source]

Get the set of blackboard keys filtered by client unique identifiers.

Parameters:client_ids (Union[Set[UUID], List[UUID]]) – set of client uuid’s.
Return type:Set[str]
Returns:subset of keys that have been registered by the specified clients
static keys_filtered_by_regex(regex)[source]

Get the set of blackboard keys filtered by regex.

Parameters:regex (str) – a python regex string
Return type:Set[str]
Returns:subset of keys that have been registered and match the pattern
static relative_name(namespace, key)[source]

Examples

'/' + 'foo'  = '/foo'
'/' + '/foo' = '/foo'
'/foo' + 'bar' = '/foo/bar'
'/foo/' + 'bar' = '/foo/bar'
'/foo' + '/bar' => KeyError('/bar' is not in 'foo')
'/foo' + 'foo/bar' = '/foo/foo/bar'
Parameters:
  • namespace (str) – namespace the key should be embedded in
  • key (str) – key name (relative or absolute)
Return type:

str

Returns:

the absolute name

Raises:

KeyError if the key is not in the specified namespace

Warning

To expedite the method call (it’s used with high frequency in blackboard key lookups), no checks are made to ensure the namespace argument leads with a “/”

static set(variable_name, value)[source]

Set the value associated with the given a variable name, can be nested, e.g. battery.percentage. This differs from the client get method in that it doesn’t pass through the client access checks. To be used for utility tooling (e.g. display methods) and not by users directly.

Parameters:variable_name (str) – of the variable to set, can be nested, e.g. battery.percentage
Raises:AttributeError – if it is attempting to set a nested attribute tha does not exist.
static unset(key)[source]

For when you need to completely remove a blackboard variable (key-value pair), this provides a convenient helper method.

Parameters:key (str) – name of the variable to remove
Returns:True if the variable was removed, False if it was already absent
class py_trees.blackboard.Client(*, name=None, namespace=None)[source]

Bases: object

Client to the key-value store for sharing data between behaviours.

Examples

Blackboard clients will accept a user-friendly name or create one for you if none is provided. Regardless of what name is chosen, clients are always uniquely identified via a uuid generated on construction.

provided = py_trees.blackboard.Client(name="Provided")
print(provided)
generated = py_trees.blackboard.Client()
print(generated)
_images/blackboard_client_instantiation.png

Client Instantiation

Register read/write access for keys on the blackboard. Note, registration is not initialisation.

blackboard = py_trees.blackboard.Client(name="Client")
blackboard.register_key(key="foo", access=py_trees.common.Access.WRITE)
blackboard.register_key(key="bar", access=py_trees.common.Access.READ)
blackboard.foo = "foo"
print(blackboard)
_images/blackboard_read_write.png

Variable Read/Write Registration

Keys and clients can make use of namespaces, designed by the ‘/’ char. Most methods permit a flexible expression of either relative or absolute names.

blackboard = py_trees.blackboard.Client(name="Global")
parameters = py_trees.blackboard.Client(name="Parameters", namespace="parameters")

blackboard.register_key(key="foo", access=py_trees.common.Access.WRITE)
blackboard.register_key(key="/bar", access=py_trees.common.Access.WRITE)
blackboard.register_key(key="/parameters/default_speed", access=py_trees.common.Access.WRITE)
parameters.register_key(key="aggressive_speed", access=py_trees.common.Access.WRITE)

blackboard.foo = "foo"
blackboard.bar = "bar"
blackboard.parameters.default_speed = 20.0
parameters.aggressive_speed = 60.0

miss_daisy = blackboard.parameters.default_speed
van_diesel = parameters.aggressive_speed

print(blackboard)
print(parameters)
_images/blackboard_namespaces.png

Namespaces and Namespaced Clients

Disconnected instances will discover the centralised key-value store.

def check_foo():
    blackboard = py_trees.blackboard.Client(name="Reader")
    blackboard.register_key(key="foo", access=py_trees.common.Access.READ)
    print("Foo: {}".format(blackboard.foo))


blackboard = py_trees.blackboard.Client(name="Writer")
blackboard.register_key(key="foo", access=py_trees.common.Access.WRITE)
blackboard.foo = "bar"
check_foo()

To respect an already initialised key on the blackboard:

blackboard = Client(name="Writer")
blackboard.register_key(key="foo", access=py_trees.common.Access.READ)
result = blackboard.set("foo", "bar", overwrite=False)

Store complex objects on the blackboard:

class Nested(object):
    def __init__(self):
        self.foo = None
        self.bar = None

    def __str__(self):
        return str(self.__dict__)


writer = py_trees.blackboard.Client(name="Writer")
writer.register_key(key="nested", access=py_trees.common.Access.WRITE)
reader = py_trees.blackboard.Client(name="Reader")
reader.register_key(key="nested", access=py_trees.common.Access.READ)

writer.nested = Nested()
writer.nested.foo = "I am foo"
writer.nested.bar = "I am bar"

foo = reader.nested.foo
print(writer)
print(reader)
_images/blackboard_nested.png

Log and display the activity stream:

py_trees.blackboard.Blackboard.enable_activity_stream(maximum_size=100)
reader = py_trees.blackboard.Client(name="Reader")
reader.register_key(key="foo", access=py_trees.common.Access.READ)
writer = py_trees.blackboard.Client(name="Writer")
writer.register_key(key="foo", access=py_trees.common.Access.WRITE)
writer.foo = "bar"
writer.foo = "foobar"
unused_result = reader.foo
print(py_trees.display.unicode_blackboard_activity_stream())
py_trees.blackboard.Blackboard.activity_stream.clear()
_images/blackboard_activity_stream.png

Display the blackboard on the console, or part thereof:

writer = py_trees.blackboard.Client(name="Writer")
for key in {"foo", "bar", "dude", "dudette"}:
    writer.register_key(key=key, access=py_trees.common.Access.WRITE)

reader = py_trees.blackboard.Client(name="Reader")
for key in {"foo", "bar"}:
    reader.register_key(key="key", access=py_trees.common.Access.READ)

writer.foo = "foo"
writer.bar = "bar"
writer.dude = "bob"

# all key-value pairs
print(py_trees.display.unicode_blackboard())
# various filtered views
print(py_trees.display.unicode_blackboard(key_filter={"foo"}))
print(py_trees.display.unicode_blackboard(regex_filter="dud*"))
print(py_trees.display.unicode_blackboard(client_filter={reader.unique_identifier}))
# list the clients associated with each key
print(py_trees.display.unicode_blackboard(display_only_key_metadata=True))
_images/blackboard_display.png

Behaviours are not automagically connected to the blackboard but you may manually attach one or more clients so that associations between behaviours and variables can be tracked - this is very useful for introspection and debugging.

Creating a custom behaviour with blackboard variables:

class Foo(py_trees.behaviour.Behaviour):

    def __init__(self, name):
        super().__init__(name=name)
        self.blackboard = self.attach_blackboard_client(name="Foo Global")
        self.parameters = self.attach_blackboard_client(name="Foo Params", namespace="foo_parameters_")
        self.state = self.attach_blackboard_client(name="Foo State", namespace="foo_state_")

        # create a key 'foo_parameters_init' on the blackboard
        self.parameters.register_key("init", access=py_trees.common.Access.READ)
        # create a key 'foo_state_number_of_noodles' on the blackboard
        self.state.register_key("number_of_noodles", access=py_trees.common.Access.WRITE)

    def initialise(self):
        self.state.number_of_noodles = self.parameters.init

    def update(self):
        self.state.number_of_noodles += 1
        self.feedback_message = self.state.number_of_noodles
        if self.state.number_of_noodles > 5:
            return py_trees.common.Status.SUCCESS
        else:
            return py_trees.common.Status.RUNNING


# could equivalently do directly via the Blackboard static methods if
# not interested in tracking / visualising the application configuration
configuration = py_trees.blackboard.Client(name="App Config")
configuration.register_key("foo_parameters_init", access=py_trees.common.Access.WRITE)
configuration.foo_parameters_init = 3

foo = Foo(name="The Foo")
for i in range(1, 8):
    foo.tick_once()
    print("Number of Noodles: {}".format(foo.feedback_message))

Rendering a dot graph for a behaviour tree, complete with blackboard variables:

# in code
py_trees.display.render_dot_tree(py_trees.demos.blackboard.create_root())
# command line tools
py-trees-render --with-blackboard-variables py_trees.demos.blackboard.create_root
digraph pastafarianism {
graph [fontname="times-roman"];
node [fontname="times-roman"];
edge [fontname="times-roman"];
"Blackboard Demo" [label="Blackboard Demo", shape=box, style=filled, fillcolor=orange, fontsize=9, fontcolor=black];
"Set Nested" [label="Set Nested", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
"Blackboard Demo" -> "Set Nested";
Writer [label=Writer, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
"Blackboard Demo" -> Writer;
"Check Nested Foo" [label="Check Nested Foo", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
"Blackboard Demo" -> "Check Nested Foo";
ParamsAndState [label=ParamsAndState, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
"Blackboard Demo" -> ParamsAndState;
subgraph  {
label="children_of_Blackboard Demo";
rank=same;
"Set Nested" [label="Set Nested", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Writer [label=Writer, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
"Check Nested Foo" [label="Check Nested Foo", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
ParamsAndState [label=ParamsAndState, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
}

Configuration [label=Configuration, shape=ellipse, style=filled, color=blue, fillcolor=gray, fontsize=7, fontcolor=blue];
"/dude" [label="/dude: Bob", shape=box, style=filled, color=blue, fillcolor=white, fontsize=8, fontcolor=blue, width=0, height=0, fixedsize=False];
"/dude" -> Writer  [color=blue, constraint=False];
Configuration -> "/dude"  [color=blue, constraint=False];
"/parameters/default_speed" [label="/parameters/default_speed: 30.0", shape=box, style=filled, color=blue, fillcolor=white, fontsize=8, fontcolor=blue, width=0, height=0, fixedsize=False];
"/parameters/default_speed" -> ParamsAndState  [color=blue, constraint=False];
Configuration -> "/parameters/default_speed"  [color=blue, constraint=False];
"/nested" [label="/nested: -", shape=box, style=filled, color=blue, fillcolor=white, fontsize=8, fontcolor=blue, width=0, height=0, fixedsize=False];
"/nested" -> "Check Nested Foo"  [color=blue, constraint=False];
"Set Nested" -> "/nested"  [color=blue, constraint=True];
"/spaghetti" [label="/spaghetti: -", shape=box, style=filled, color=blue, fillcolor=white, fontsize=8, fontcolor=blue, width=0, height=0, fixedsize=False];
Writer -> "/spaghetti"  [color=blue, constraint=True];
"/state/current_speed" [label="/state/current_speed: -", shape=box, style=filled, color=blue, fillcolor=white, fontsize=8, fontcolor=blue, width=0, height=0, fixedsize=False];
ParamsAndState -> "/state/current_speed"  [color=blue, constraint=True];
}

Tree with Blackboard Variables

And to demonstrate that it doesn’t become a tangled nightmare at scale, an example of a more complex tree:

digraph pastafarianism {
graph [fontname="times-roman"];
node [fontname="times-roman"];
edge [fontname="times-roman"];
"Tutorial Eight" [label="Tutorial Eight\n--SuccessOnAll(-)--", shape=parallelogram, style=filled, fillcolor=gold, fontsize=9, fontcolor=black];
Topics2BB [label=Topics2BB, shape=box, style=filled, fillcolor=orange, fontsize=9, fontcolor=black];
"Tutorial Eight" -> Topics2BB;
Scan2BB [label=Scan2BB, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Topics2BB -> Scan2BB;
Cancel2BB [label=Cancel2BB, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Topics2BB -> Cancel2BB;
Battery2BB [label=Battery2BB, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Topics2BB -> Battery2BB;
subgraph  {
label=children_of_Topics2BB;
rank=same;
Scan2BB [label=Scan2BB, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Cancel2BB [label=Cancel2BB, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Battery2BB [label=Battery2BB, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
}

Tasks [label=Tasks, shape=octagon, style=filled, fillcolor=cyan, fontsize=9, fontcolor=black];
"Tutorial Eight" -> Tasks;
"Battery Low?" [label="Battery Low?", shape=ellipse, style=filled, fillcolor=ghostwhite, fontsize=9, fontcolor=black];
Tasks -> "Battery Low?";
"Flash Red" [label="Flash Red", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
"Battery Low?" -> "Flash Red";
Scan [label=Scan, shape=box, style=filled, fillcolor=orange, fontsize=9, fontcolor=black];
Tasks -> Scan;
"Scan or Die" [label="Scan or Die", shape=octagon, style=filled, fillcolor=cyan, fontsize=9, fontcolor=black];
Scan -> "Scan or Die";
"Ere we Go" [label="Ere we Go", shape=box, style=filled, fillcolor=orange, fontsize=9, fontcolor=black];
"Scan or Die" -> "Ere we Go";
UnDock [label=UnDock, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
"Ere we Go" -> UnDock;
"Scan or Be Cancelled" [label="Scan or Be Cancelled", shape=octagon, style=filled, fillcolor=cyan, fontsize=9, fontcolor=black];
"Ere we Go" -> "Scan or Be Cancelled";
"Cancelling?" [label="Cancelling?", shape=box, style=filled, fillcolor=orange, fontsize=9, fontcolor=black];
"Scan or Be Cancelled" -> "Cancelling?";
"Cancel?" [label="Cancel?", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
"Cancelling?" -> "Cancel?";
"Move Home" [label="Move Home", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
"Cancelling?" -> "Move Home";
"Result2BB\n'cancelled'" [label="Result2BB\n'cancelled'", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
"Cancelling?" -> "Result2BB\n'cancelled'";
subgraph  {
label="children_of_Cancelling?";
rank=same;
"Cancel?" [label="Cancel?", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
"Move Home" [label="Move Home", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
"Result2BB\n'cancelled'" [label="Result2BB\n'cancelled'", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
}

"Move Out and Scan" [label="Move Out and Scan", shape=box, style=filled, fillcolor=orange, fontsize=9, fontcolor=black];
"Scan or Be Cancelled" -> "Move Out and Scan";
"Move Out" [label="Move Out", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
"Move Out and Scan" -> "Move Out";
Scanning [label="Scanning\n--SuccessOnOne--", shape=parallelogram, style=filled, fillcolor=gold, fontsize=9, fontcolor=black];
"Move Out and Scan" -> Scanning;
"Context Switch" [label="Context Switch", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Scanning -> "Context Switch";
Rotate [label=Rotate, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Scanning -> Rotate;
"Flash Blue" [label="Flash Blue", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Scanning -> "Flash Blue";
subgraph  {
label=children_of_Scanning;
rank=same;
"Context Switch" [label="Context Switch", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Rotate [label=Rotate, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
"Flash Blue" [label="Flash Blue", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
}

"Move Home*" [label="Move Home*", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
"Move Out and Scan" -> "Move Home*";
"Result2BB\n'succeeded'" [label="Result2BB\n'succeeded'", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
"Move Out and Scan" -> "Result2BB\n'succeeded'";
subgraph  {
label="children_of_Move Out and Scan";
rank=same;
"Move Out" [label="Move Out", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Scanning [label="Scanning\n--SuccessOnOne--", shape=parallelogram, style=filled, fillcolor=gold, fontsize=9, fontcolor=black];
"Move Home*" [label="Move Home*", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
"Result2BB\n'succeeded'" [label="Result2BB\n'succeeded'", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
}

subgraph  {
label="children_of_Scan or Be Cancelled";
rank=same;
"Cancelling?" [label="Cancelling?", shape=box, style=filled, fillcolor=orange, fontsize=9, fontcolor=black];
"Move Out and Scan" [label="Move Out and Scan", shape=box, style=filled, fillcolor=orange, fontsize=9, fontcolor=black];
}

Dock [label=Dock, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
"Ere we Go" -> Dock;
Celebrate [label="Celebrate\n--SuccessOnOne--", shape=parallelogram, style=filled, fillcolor=gold, fontsize=9, fontcolor=black];
"Ere we Go" -> Celebrate;
"Flash Green" [label="Flash Green", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Celebrate -> "Flash Green";
Pause [label=Pause, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Celebrate -> Pause;
subgraph  {
label=children_of_Celebrate;
rank=same;
"Flash Green" [label="Flash Green", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Pause [label=Pause, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
}

subgraph  {
label="children_of_Ere we Go";
rank=same;
UnDock [label=UnDock, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
"Scan or Be Cancelled" [label="Scan or Be Cancelled", shape=octagon, style=filled, fillcolor=cyan, fontsize=9, fontcolor=black];
Dock [label=Dock, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Celebrate [label="Celebrate\n--SuccessOnOne--", shape=parallelogram, style=filled, fillcolor=gold, fontsize=9, fontcolor=black];
}

Die [label=Die, shape=box, style=filled, fillcolor=orange, fontsize=9, fontcolor=black];
"Scan or Die" -> Die;
Notification [label="Notification\n--SuccessOnOne--", shape=parallelogram, style=filled, fillcolor=gold, fontsize=9, fontcolor=black];
Die -> Notification;
"Flash Red*" [label="Flash Red*", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Notification -> "Flash Red*";
"Pause*" [label="Pause*", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Notification -> "Pause*";
subgraph  {
label=children_of_Notification;
rank=same;
"Flash Red*" [label="Flash Red*", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
"Pause*" [label="Pause*", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
}

"Result2BB\n'failed'" [label="Result2BB\n'failed'", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Die -> "Result2BB\n'failed'";
subgraph  {
label=children_of_Die;
rank=same;
Notification [label="Notification\n--SuccessOnOne--", shape=parallelogram, style=filled, fillcolor=gold, fontsize=9, fontcolor=black];
"Result2BB\n'failed'" [label="Result2BB\n'failed'", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
}

subgraph  {
label="children_of_Scan or Die";
rank=same;
"Ere we Go" [label="Ere we Go", shape=box, style=filled, fillcolor=orange, fontsize=9, fontcolor=black];
Die [label=Die, shape=box, style=filled, fillcolor=orange, fontsize=9, fontcolor=black];
}

"Send Result" [label="Send Result", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Scan -> "Send Result";
subgraph  {
label=children_of_Scan;
rank=same;
"Scan or Die" [label="Scan or Die", shape=octagon, style=filled, fillcolor=cyan, fontsize=9, fontcolor=black];
"Send Result" [label="Send Result", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
}

Idle [label=Idle, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Tasks -> Idle;
subgraph  {
label=children_of_Tasks;
rank=same;
"Battery Low?" [label="Battery Low?", shape=ellipse, style=filled, fillcolor=ghostwhite, fontsize=9, fontcolor=black];
Scan [label=Scan, shape=box, style=filled, fillcolor=orange, fontsize=9, fontcolor=black];
Idle [label=Idle, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
}

subgraph  {
label="children_of_Tutorial Eight";
rank=same;
Topics2BB [label=Topics2BB, shape=box, style=filled, fillcolor=orange, fontsize=9, fontcolor=black];
Tasks [label=Tasks, shape=octagon, style=filled, fillcolor=cyan, fontsize=9, fontcolor=black];
}

event_scan_button [label="event_scan_button: -", shape=box, style=filled, color=blue, fillcolor=white, fontsize=8, fontcolor=blue, width=0, height=0, fixedsize=False];
Scan2BB -> event_scan_button  [color=blue, constraint=True];
event_cancel_button [label="event_cancel_button: -", shape=box, style=filled, color=blue, fillcolor=white, fontsize=8, fontcolor=blue, width=0, height=0, fixedsize=False];
event_cancel_button -> "Cancel?"  [color=blue, constraint=False];
Cancel2BB -> event_cancel_button  [color=blue, constraint=True];
battery [label="battery: sensor_msgs.msg.B...", shape=box, style=filled, color=blue, fillcolor=white, fontsize=8, fontcolor=blue, width=0, height=0, fixedsize=False];
Battery2BB -> battery  [color=blue, constraint=True];
battery_low_warning [label="battery_low_warning: False", shape=box, style=filled, color=blue, fillcolor=white, fontsize=8, fontcolor=blue, width=0, height=0, fixedsize=False];
battery_low_warning -> "Battery Low?"  [color=blue, constraint=False];
Battery2BB -> battery_low_warning  [color=blue, constraint=True];
scan_result [label="scan_result: -", shape=box, style=filled, color=blue, fillcolor=white, fontsize=8, fontcolor=blue, width=0, height=0, fixedsize=False];
scan_result -> "Send Result"  [color=blue, constraint=False];
"Result2BB\n'cancelled'" -> scan_result  [color=blue, constraint=True];
"Result2BB\n'succeeded'" -> scan_result  [color=blue, constraint=True];
"Result2BB\n'failed'" -> scan_result  [color=blue, constraint=True];
}

A more complex tree

Debug deeper with judicious application of the tree, blackboard and activity stream display methods around the tree tick (refer to py_trees.visitors.DisplaySnapshotVisitor for examplar code):

_images/blackboard_trees.png

Tree level debugging

Variables:
  • name (str) – client’s convenient, but not necessarily unique identifier
  • namespace (str) – apply this as a prefix to any key/variable name operations
  • unique_identifier (uuid.UUID) – client’s unique identifier
  • read (typing.Set[str]) – set of absolute key names with read access
  • write (typing.Set[str]) – set of absolute key names with write access
  • exclusive (typing.Set[str]) – set of absolute key names with exclusive write access
  • required (typing.Set[str]) – set of absolute key names required to have data present
  • (typing.Dict[str, str] (remappings) – client key names with blackboard remappings
  • (typing.Set[str] (namespaces) – a cached list of namespaces this client accesses
__getattr__(name)[source]

Convenience attribute style referencing with checking against permissions.

Raises:
  • AttributeError – if the client does not have read access to the variable
  • KeyError – if the variable does not yet exist on the blackboard
__init__(*, name=None, namespace=None)[source]
Parameters:
  • name (Optional[str]) – client’s convenient identifier (stringifies the uuid if None)
  • namespace (Optional[str]) – prefix to apply to key/variable name operations
  • read – list of keys for which this client has read access
  • write – list of keys for which this client has write access
  • exclusive – list of keys for which this client has exclusive write access
Raises:
  • TypeError – if the provided name is not of type str
  • ValueError – if the unique identifier has already been registered
__setattr__(name, value)[source]

Convenience attribute style referencing with checking against permissions.

Raises:AttributeError – if the client does not have write access to the variable
__str__()[source]

Return str(self).

__weakref__

list of weak references to the object (if defined)

absolute_name(key)[source]

Generate the fully qualified key name for this key.

blackboard = Client(name="FooBar", namespace="foo")
blackboard.register_key(key="bar", access=py_trees.common.Access.READ)
print("{}".format(blackboard.absolute_name("bar")))  # "/foo/bar"
Parameters:key (str) – name of the key
Return type:str
Returns:the absolute name
Raises:KeyError – if the key is not registered with this client
exists(name)[source]

Check if the specified variable exists on the blackboard.

Parameters:name (str) – name of the variable to get, can be nested, e.g. battery.percentage
Raises:AttributeError – if the client does not have read access to the variable
Return type:bool
get(name)[source]

Method based accessor to the blackboard variables (as opposed to simply using ‘.<name>’).

Parameters:

name (str) – name of the variable to get, can be nested, e.g. battery.percentage

Raises:
  • AttributeError – if the client does not have read access to the variable
  • KeyError – if the variable or it’s nested attributes do not yet exist on the blackboard
Return type:

Any

id()[source]

The unique identifier for this client.

Return type:UUID
Returns:The uuid.UUID object
is_registered(key, access=None)[source]

Check to see if the specified key is registered.

Parameters:
  • key (str) – in either relative or absolute form
  • access (Union[None, Access]) – access property, if None, just checks for registration, regardless of property
Return type:

bool

Returns:

if registered, True otherwise False

register_key(key, access, required=False, remap_to=None)[source]

Register a key on the blackboard to associate with this client.

Parameters:
  • key (str) – key to register
  • access (Access) – access level (read, write, exclusive write)
  • required (bool) – if true, check key exists when calling verify_required_keys_exist()
  • remap_to (Optional[str]) – remap the key to this location on the blackboard

Note the remap simply changes the storage location. From the perspective of the client, access via the specified ‘key’ remains the same.

Raises:
  • AttributeError if exclusive write access is requested, but write access has already been given to another client
  • TypeError if the access argument is of incorrect type
set(name, value, overwrite=True)[source]

Set, conditionally depending on whether the variable already exists or otherwise.

This is most useful when initialising variables and multiple elements seek to do so. A good policy to adopt for your applications in these situations is a first come, first served policy. Ensure global configuration has the first opportunity followed by higher priority behaviours in the tree and so forth. Lower priority behaviours would use this to respect the pre-configured setting and at most, just validate that it is acceptable to the functionality of it’s own behaviour.

Parameters:
  • name (str) – name of the variable to set
  • value (Any) – value of the variable to set
  • overwrite (bool) – do not set if the variable already exists on the blackboard
Return type:

bool

Returns:

success or failure (overwrite is False and variable already set)

Raises:
  • AttributeError – if the client does not have write access to the variable
  • KeyError – if the variable does not yet exist on the blackboard
unregister(clear=True)[source]

Unregister this blackboard client and if requested, clear key-value pairs if this client is the last user of those variables.

Parameters:clear (bool) – remove key-values pairs from the blackboard
unregister_all_keys(clear=True)[source]

Unregister all keys currently registered by this blackboard client and if requested, clear key-value pairs if this client is the last user of those variables.

Parameters:clear (bool) – remove key-values pairs from the blackboard
unregister_key(key, clear=True, update_namespace_cache=True)[source]

Unegister a key associated with this client.

Parameters:
  • key (str) – key to unregister
  • clear (bool) – remove key-values pairs from the blackboard
  • update_namespace_cache (bool) – disable if you are batching

A method that batches calls to this method is unregister_all_keys().

Raises:KeyError if the key has not been previously registered
unset(key)[source]

For when you need to completely remove a blackboard variable (key-value pair), this provides a convenient helper method.

Parameters:key (str) – name of the variable to remove
Returns:True if the variable was removed, False if it was already absent
verify_required_keys_exist()[source]

En-masse check of existence on the blackboard for all keys that were hitherto registered as ‘required’.

Raises: KeyError if any of the required keys do not exist on the blackboard

class py_trees.blackboard.KeyMetaData[source]

Bases: object

Stores the aggregated metadata for a key on the blackboard.

__init__()[source]

Initialize self. See help(type(self)) for accurate signature.

__weakref__

list of weak references to the object (if defined)

py_trees.common

Common definitions, methods and variables used by the py_trees library.

class py_trees.common.BlackBoxLevel[source]

Bases: enum.IntEnum

Whether a behaviour is a blackbox entity that may be considered collapsible (i.e. everything in its subtree will not be visualised) by visualisation tools.

Blackbox levels are increasingly persistent in visualisations.

Visualisations by default, should always collapse blackboxes that represent DETAIL.

BIG_PICTURE = 3

A blackbox that represents a big picture part of the entire tree view.

COMPONENT = 2

A blackbox that encapsulates a subgroup of functionalities as a single group.

DETAIL = 1

A blackbox that encapsulates detailed activity.

NOT_A_BLACKBOX = 4

Not a blackbox, do not ever collapse.

class py_trees.common.ClearingPolicy[source]

Bases: enum.IntEnum

Policy rules for behaviours to dictate when data should be cleared/reset.

NEVER = 3

Never clear the data

ON_INITIALISE = 1

Clear when entering the initialise() method.

ON_SUCCESS = 2

Clear when returning SUCCESS.

class py_trees.common.Duration[source]

Bases: enum.Enum

Naming conventions.

INFINITE = inf

INFINITE oft used for perpetually blocking operations.

UNTIL_THE_BATTLE_OF_ALFREDO = inf

UNTIL_THE_BATTLE_OF_ALFREDO is an alias for INFINITE.

class py_trees.common.Name[source]

Bases: enum.Enum

Naming conventions.

AUTO_GENERATED = 'AUTO_GENERATED'

AUTO_GENERATED leaves it to the behaviour to generate a useful, informative name.

class py_trees.common.ParallelPolicy[source]

Configurable policies for Parallel behaviours.

class SuccessOnAll(synchronise=True)[source]

Return SUCCESS only when each and every child returns SUCCESS. If synchronisation is requested, any children that tick with SUCCESS will be skipped on subsequent ticks until the policy criteria is met, or one of the children returns status FAILURE.

class SuccessOnOne[source]

Return SUCCESS so long as at least one child has SUCCESS and the remainder are RUNNING

class SuccessOnSelected(children, synchronise=True)[source]

Return SUCCESS so long as each child in a specified list returns SUCCESS. If synchronisation is requested, any children that tick with SUCCESS will be skipped on subsequent ticks until the policy criteria is met, or one of the children returns status FAILURE.

class py_trees.common.Status[source]

Bases: enum.Enum

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 inactive, i.e. this is the status before first entry, and after a higher priority switch has occurred.

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.

class py_trees.common.VisibilityLevel[source]

Bases: enum.IntEnum

Closely associated with the BlackBoxLevel for a behaviour. This sets the visibility level to be used for visualisations.

Visibility levels correspond to reducing levels of visibility in a visualisation.

ALL = 0

Do not collapse any behaviour.

BIG_PICTURE = 3

Collapse any blackbox that isn’t marked with BIG_PICTURE.

COMPONENT = 2

Collapse blackboxes marked with COMPONENT or lower.

DETAIL = 1

Collapse blackboxes marked with DETAIL or lower.

common.string_to_visibility_level()

Will convert a string to a visibility level. Note that it will quietly return ALL if the string is not matched to any visibility level string identifier.

Parameters:level (str) – visibility level as a string
Returns:visibility level enum
Return type:VisibilityLevel

py_trees.composites

Composites are responsible for directing the path traced through the tree on a given tick (execution). They are the factories (Sequences and Parallels) and decision makers (Selectors) of a behaviour tree.

digraph selector {
graph [fontname="times-roman"];
node [fontname="times-roman"];
edge [fontname="times-roman"];
Sequence [fontcolor=black, shape=box, fontsize=11, style=filled, fillcolor=orange];
Selector [fontcolor=black, shape=octagon, fontsize=11, style=filled, fillcolor=cyan];
Parallel [fontcolor=black, shape=parallelogram, fontsize=11, style=filled, fillcolor=gold];
}

PyTree Composites

Composite behaviours typically manage children and apply some logic to the way they execute and return a result, but generally don’t do anything themselves. Perform the checks or actions you need to do in the non-composite behaviours.

Most any desired functionality can be authored with a combination of these three composites. In fact, it is precisely this feature that makes behaviour trees attractive - it breaks down complex decision making logic to just three primitive elements. It is possible and often desirable to extend this set with custom composites of your own, but think carefully before you do - in almost every case, a combination of the existing composites will serve and as a result, you will merely compound the complexity inherent in your tree logic. This this makes it confoundingly difficult to design, introspect and debug. As an example, design sessions often revolve around a sketched graph on a whiteboard. When these graphs are composed of just five elements (Selectors, Sequences, Parallels, Decorators and Behaviours), it is very easy to understand the logic at a glance. Double the number of fundamental elements and you may as well be back at the terminal parsing code.

Tip

You should never need to subclass or create new composites.

The basic operational modes of the three composites in this library are as follows:

  • Selector: select a child to execute based on cascading priorities
  • Sequence: execute children sequentially
  • Parallel: execute children concurrently

This library does provide some flexibility in how each composite is implemented without breaking the fundamental nature of each (as described above). Selectors and Sequences can be configured with or without memory (resumes or resets if children are RUNNING) and the results of a parallel can be configured to wait upon all children completing, succeed on one, all or a subset thereof.

Tip

Follow the links in each composite’s documentation to the relevant demo programs.

class py_trees.composites.Composite(name=<Name.AUTO_GENERATED: 'AUTO_GENERATED'>, children=None)[source]

Bases: py_trees.behaviour.Behaviour

The parent class to all composite behaviours, i.e. those that have children.

Parameters:
  • name (str) – the composite behaviour name
  • children ([Behaviour]) – list of children to add
__init__(name=<Name.AUTO_GENERATED: 'AUTO_GENERATED'>, children=None)[source]

Initialize self. See help(type(self)) for accurate signature.

add_child(child)[source]

Adds a child.

Parameters:

child (Behaviour) – child to add

Raises:
Returns:

unique id of the child

Return type:

uuid.UUID

add_children(children)[source]

Append a list of children to the current list.

Parameters:children ([Behaviour]) – list of children to add
insert_child(child, index)[source]

Insert child at the specified index. This simply directly calls the python list’s insert method using the child and index arguments.

Parameters:
  • child (Behaviour) – child to insert
  • index (int) – index to insert it at
Returns:

unique id of the child

Return type:

uuid.UUID

prepend_child(child)[source]

Prepend the child before all other children.

Parameters:child (Behaviour) – child to insert
Returns:unique id of the child
Return type:uuid.UUID
remove_all_children()[source]

Remove all children. Makes sure to stop each child if necessary.

remove_child(child)[source]

Remove the child behaviour from this composite.

Parameters:child (Behaviour) – child to delete
Returns:index of the child that was removed
Return type:int

Todo

Error handling for when child is not in this list

remove_child_by_id(child_id)[source]

Remove the child with the specified id.

Parameters:child_id (uuid.UUID) – unique id of the child
Raises:IndexError – if the child was not found
replace_child(child, replacement)[source]

Replace the child behaviour with another.

Parameters:
stop(new_status=<Status.INVALID: 'INVALID'>)[source]

There is generally two use cases that must be supported here.

1) Whenever the composite has gone to a recognised state (i.e. FAILURE or SUCCESS), or 2) when a higher level parent calls on it to truly stop (INVALID).

In only the latter case will children need to be forcibly stopped as well. In the first case, they will have stopped themselves appropriately already.

Parameters:new_status (Status) – behaviour will transition to this new status
tip()[source]

Recursive function to extract the last running node of the tree.

Returns:class::~py_trees.behaviour.Behaviour: the tip function of the current child of this composite or None
class py_trees.composites.Parallel(name=<Name.AUTO_GENERATED: 'AUTO_GENERATED'>, policy=<py_trees.common.ParallelPolicy.SuccessOnAll object>, children=None)[source]

Bases: py_trees.composites.Composite

Parallels enable a kind of concurrency

digraph pastafarianism {
graph [fontname="times-roman", splines=curved];
node [fontname="times-roman"];
edge [fontname="times-roman"];
Parallel [fillcolor=gold, fontcolor=black, fontsize=9, label="Parallel\n--SuccessOnSelected(⚡,[B1,B2])--", shape=parallelogram, style=filled];
B1 [fillcolor=gray, fontcolor=black, fontsize=9, label=B1, shape=ellipse, style=filled];
Parallel -> B1;
B2 [fillcolor=gray, fontcolor=black, fontsize=9, label=B2, shape=ellipse, style=filled];
Parallel -> B2;
B3 [fillcolor=gray, fontcolor=black, fontsize=9, label=B3, shape=ellipse, style=filled];
Parallel -> B3;
}

Ticks every child every time the parallel is run (a poor man’s form of parallelism).

Policies SuccessOnAll and SuccessOnSelected may be configured to be synchronised in which case children that tick with SUCCESS will be skipped on subsequent ticks until the policy criteria is met, or one of the children returns status FAILURE.

Parallels with policy SuccessOnSelected will check in both the setup() and tick() methods to to verify the selected set of children is actually a subset of the children of this parallel.

__init__(name=<Name.AUTO_GENERATED: 'AUTO_GENERATED'>, policy=<py_trees.common.ParallelPolicy.SuccessOnAll object>, children=None)[source]
Parameters:
  • name (str) – the composite behaviour name
  • policy (ParallelPolicy) – policy to use for deciding success or otherwise
  • children ([Behaviour]) – list of children to add
setup(**kwargs)[source]

Detect before ticking whether the policy configuration is invalid.

Parameters:

**kwargs (dict) – distribute arguments to this behaviour and in turn, all of it’s children

Raises:
  • RuntimeError – if the parallel’s policy configuration is invalid
  • Exception – be ready to catch if any of the children raise an exception
stop(new_status=<Status.INVALID: 'INVALID'>)[source]

For interrupts or any of the termination conditions, ensure that any running children are stopped.

Parameters:new_status (Status) – the composite is transitioning to this new status
tick()[source]

Tick over the children.

Yields:Behaviour – a reference to itself or one of its children
Raises:RuntimeError – if the policy configuration was invalid
validate_policy_configuration()[source]

Policy configuration can be invalid if:

  • Policy is SuccessOnSelected and no behaviours have been specified
  • Policy is SuccessOnSelected and behaviours that are not children exist
Raises:RuntimeError – if policy configuration was invalid
class py_trees.composites.Selector(name='Selector', memory=False, children=None)[source]

Bases: py_trees.composites.Composite

Selectors are the decision makers.

digraph selector {
graph [fontname="times-roman"];
node [fontname="times-roman"];
edge [fontname="times-roman"];
Selector [fontcolor=black, shape=octagon, fontsize=11, style=filled, fillcolor=cyan];
"High Priority" [fontcolor=black, shape=ellipse, fontsize=11, style=filled, fillcolor=gray];
Selector -> "High Priority";
"Med Priority" [fontcolor=black, shape=ellipse, fontsize=11, style=filled, fillcolor=gray];
Selector -> "Med Priority";
"Low Priority" [fontcolor=black, shape=ellipse, fontsize=11, style=filled, fillcolor=gray];
Selector -> "Low Priority";
}

A selector executes each of its child behaviours in turn until one of them succeeds (at which point it itself returns RUNNING or SUCCESS, or it runs out of children at which point it itself returns FAILURE. We usually refer to selecting children as a means of choosing between priorities. Each child and its subtree represent a decreasingly lower priority path.

Note

Switching from a low -> high priority branch causes a stop(INVALID) signal to be sent to the previously executing low priority branch. This signal will percolate down that child’s own subtree. Behaviours should make sure that they catch this and destruct appropriately.

See also

The py-trees-demo-selector program demos higher priority switching under a selector.

Parameters:
  • name (str) – the composite behaviour name
  • memory (bool) – if RUNNING on the previous tick, resume with the RUNNING child
  • children ([Behaviour]) – list of children to add
__init__(name='Selector', memory=False, children=None)[source]

Initialize self. See help(type(self)) for accurate signature.

stop(new_status=<Status.INVALID: 'INVALID'>)[source]

Stopping a selector requires setting the current child to none. Note that it is important to implement this here instead of terminate, so users are free to subclass this easily with their own terminate and not have to remember that they need to call this function manually.

Parameters:new_status (Status) – the composite is transitioning to this new status
tick()[source]

Run the tick behaviour for this selector. Note that the status of the tick is always determined by its children, not by the user customised update function.

Yields:Behaviour – a reference to itself or one of its children
class py_trees.composites.Sequence(name='Sequence', memory=True, children=None)[source]

Bases: py_trees.composites.Composite

Sequences are the factory lines of Behaviour Trees

digraph sequence {
graph [fontname="times-roman"];
node [fontname="times-roman"];
edge [fontname="times-roman"];
Sequence [fillcolor=orange, fontcolor=black, fontsize=11, shape=box, style=filled];
Guard [fillcolor=gray, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
Sequence -> Guard;
"Action 1" [fillcolor=gray, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
Sequence -> "Action 1";
"Action 2" [fillcolor=gray, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
Sequence -> "Action 2";
"Action 3" [fillcolor=gray, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
Sequence -> "Action 3";
}

A sequence will progressively tick over each of its children so long as each child returns SUCCESS. If any child returns FAILURE or RUNNING the sequence will halt and the parent will adopt the result of this child. If it reaches the last child, it returns with that result regardless.

Note

The sequence halts once it sees a child is RUNNING and then returns the result. It does not get stuck in the running behaviour.

See also

The py-trees-demo-sequence program demos a simple sequence in action.

Parameters:
__init__(name='Sequence', memory=True, children=None)[source]

Initialize self. See help(type(self)) for accurate signature.

tick()[source]

Tick over the children.

Yields:Behaviour – a reference to itself or one of its children

py_trees.console

Simple colour definitions and syntax highlighting for the console.


Colour Definitions

The current list of colour definitions include:

  • Regular: black, red, green, yellow, blue, magenta, cyan, white,
  • Bold: bold, bold_black, bold_red, bold_green, bold_yellow, bold_blue, bold_magenta, bold_cyan, bold_white

These colour definitions can be used in the following way:

import py_trees.console as console
print(console.cyan + "    Name" + console.reset + ": " + console.yellow + "Dude" + console.reset)
py_trees.console.colours = ['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '']

List of all available colours.

py_trees.console.console_has_colours()[source]

Detects if the console (stdout) has colourising capability.

py_trees.console.define_symbol_or_fallback(original, fallback, encoding='UTF-8')[source]

Return the correct encoding according to the specified encoding. Used to make sure we get an appropriate symbol, even if the shell is merely ascii as is often the case on, e.g. Jenkins CI.

Parameters:
  • original (str) – the unicode string (usually just a character)
  • fallback (str) – the fallback ascii string
  • encoding (str, optional) – the encoding to check against.
Returns:

either original or fallback depending on whether exceptions were thrown.

Return type:

str

py_trees.console.has_colours = False

Whether the loading program has access to colours or not.

py_trees.console.has_unicode(encoding='UTF-8')[source]

Define whether the specified encoding has unicode symbols. Usually used to check if the stdout is capable or otherwise (e.g. Jenkins CI can often be configured with unicode disabled).

Parameters:encoding (str, optional) – the encoding to check against.
Returns:true if capable, false otherwise
Return type:bool
py_trees.console.logdebug(message)[source]

Prefixes [DEBUG] and colours the message green.

Parameters:message (str) – message to log.
py_trees.console.logerror(message)[source]

Prefixes [ERROR] and colours the message red.

Parameters:message (str) – message to log.
py_trees.console.logfatal(message)[source]

Prefixes [FATAL] and colours the message bold red.

Parameters:message (str) – message to log.
py_trees.console.loginfo(message)[source]

Prefixes [ INFO] to the message.

Parameters:message (str) – message to log.
py_trees.console.logwarn(message)[source]

Prefixes [ WARN] and colours the message yellow.

Parameters:message (str) – message to log.
py_trees.console.read_single_keypress()[source]

Waits for a single keypress on stdin.

This is a silly function to call if you need to do it a lot because it has to store stdin’s current setup, setup stdin for reading single keystrokes then read the single keystroke then revert stdin back after reading the keystroke.

Returns:the character of the key that was pressed
Return type:int
Raises:KeyboardInterrupt – if CTRL-C was pressed (keycode 0x03)

py_trees.decorators

Decorators are behaviours that manage a single child and provide common modifications to their underlying child behaviour (e.g. inverting the result). That is, they provide a means for behaviours to wear different ‘hats’ and this combinatorially expands the capabilities of your behaviour library.

_images/many-hats.png

An example:

digraph life {
graph [fontname="times-roman"];
node [fontname="times-roman"];
edge [fontname="times-roman"];
Life [fillcolor=orange, fontcolor=black, fontsize=11, shape=box, style=filled];
Inverter [fillcolor=ghostwhite, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
Life -> Inverter;
"Busy?" [fillcolor=gray, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
Inverter -> "Busy?";
Timeout [fillcolor=ghostwhite, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
Life -> Timeout;
"Have a Beer!" [fillcolor=gray, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
Timeout -> "Have a Beer!";
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import py_trees.decorators
import py_trees.display

if __name__ == '__main__':

    root = py_trees.composites.Sequence(name="Life")
    timeout = py_trees.decorators.Timeout(
        name="Timeout",
        child=py_trees.behaviours.Success(name="Have a Beer!")
    )
    failure_is_success = py_trees.decorators.Inverter(
        name="Inverter",
        child=py_trees.behaviours.Success(name="Busy?")
        )
    root.add_children([failure_is_success, timeout])
    py_trees.display.render_dot_tree(root)

Decorators (Hats)

Decorators with very specific functionality:

And the X is Y family:

Decorators for Blocking Behaviours

It is worth making a note of the effect of decorators on behaviours that return RUNNING for some time before finally returning SUCCESS or FAILURE (blocking behaviours) since the results are often at first, surprising.

A decorator, such as py_trees.decorators.RunningIsSuccess() on a blocking behaviour will immediately terminate the underlying child and re-intialise on it’s next tick. This is necessary to ensure the underlying child isn’t left in a dangling state (i.e. RUNNING), but is often not what is being sought.

The typical use case being attempted is to convert the blocking behaviour into a non-blocking behaviour. If the underlying child has no state being modified in either the initialise() or terminate() methods (e.g. machinery is entirely launched at init or setup time), then conversion to a non-blocking representative of the original succeeds. Otherwise, another approach is needed. Usually this entails writing a non-blocking counterpart, or combination of behaviours to affect the non-blocking characteristics.

class py_trees.decorators.Condition(child, name=<Name.AUTO_GENERATED: 'AUTO_GENERATED'>, status=<Status.SUCCESS: 'SUCCESS'>)[source]

Bases: py_trees.decorators.Decorator

Encapsulates a behaviour and wait for it’s status to flip to the desired state. This behaviour will tick with RUNNING while waiting and SUCCESS when the flip occurs.

update()[source]

SUCCESS if the decorated child has returned the specified status, otherwise RUNNING. This decorator will never return FAILURE

Returns:the behaviour’s new status Status
Return type:Status
class py_trees.decorators.Decorator(child, name=<Name.AUTO_GENERATED: 'AUTO_GENERATED'>)[source]

Bases: py_trees.behaviour.Behaviour

A decorator is responsible for handling the lifecycle of a single child beneath

Parameters:
  • child (Behaviour) – the child to be decorated
  • name – the decorator name
Raises:

TypeError – if the child is not an instance of Behaviour

stop(new_status)[source]

As with other composites, it checks if the child is running and stops it if that is the case.

Parameters:new_status (Status) – the behaviour is transitioning to this new status
tick()[source]

A decorator’s tick is exactly the same as a normal proceedings for a Behaviour’s tick except that it also ticks the decorated child node.

Yields:Behaviour – a reference to itself or one of its children
tip()[source]

Get the tip of this behaviour’s subtree (if it has one) after it’s last tick. This corresponds to the the deepest node that was running before the subtree traversal reversed direction and headed back to this node.

Returns:child behaviour, itself or None if its status is INVALID
Return type:Behaviour or None
class py_trees.decorators.EternalGuard(*, child, condition, blackboard_keys=[], name=<Name.AUTO_GENERATED: 'AUTO_GENERATED'>)[source]

Bases: py_trees.decorators.Decorator

A decorator that continually guards the execution of a subtree. If at any time the guard’s condition check fails, then the child behaviour/subtree is invalidated.

Note

This decorator’s behaviour is stronger than the guard typical of a conditional check at the beginning of a sequence of tasks as it continues to check on every tick whilst the task (or sequence of tasks) runs.

Parameters:
  • child (Behaviour) – the child behaviour or subtree
  • condition (Any) – a functional check that determines execution or not of the subtree
  • blackboard_keys (Union[List[str], Set[str]]) – provide read access for the conditional function to these keys
  • name (Union[str, Name]) – the decorator name

Examples:

Simple conditional function returning True/False:

def check():
     return True

foo = py_trees.behaviours.Foo()
eternal_guard = py_trees.decorators.EternalGuard(
    name="Eternal Guard",
    condition=check,
    child=foo
)

Simple conditional function returning SUCCESS/FAILURE:

def check():
     return py_trees.common.Status.SUCCESS

foo = py_trees.behaviours.Foo()
eternal_guard = py_trees.decorators.EternalGuard(
    name="Eternal Guard",
    condition=check,
    child=foo
)

Conditional function that makes checks against data on the blackboard (the blackboard client with pre-configured access is provided by the EternalGuard instance):

def check(blackboard):
     return blackboard.velocity > 3.0

foo = py_trees.behaviours.Foo()
eternal_guard = py_trees.decorators.EternalGuard(
    name="Eternal Guard",
    condition=check,
    blackboard_keys={"velocity"},
    child=foo
)
tick()[source]

A decorator’s tick is exactly the same as a normal proceedings for a Behaviour’s tick except that it also ticks the decorated child node.

Yields:Behaviour – a reference to itself or one of its children
update()[source]

The update method is only ever triggered in the child’s post-tick, which implies that the condition has already been checked and passed (refer to the tick() method).

class py_trees.decorators.FailureIsRunning(child, name=<Name.AUTO_GENERATED: 'AUTO_GENERATED'>)[source]

Bases: py_trees.decorators.Decorator

Dont stop running.

update()[source]

Return the decorated child’s status unless it is FAILURE in which case, return RUNNING.

Returns:the behaviour’s new status Status
Return type:Status
class py_trees.decorators.FailureIsSuccess(child, name=<Name.AUTO_GENERATED: 'AUTO_GENERATED'>)[source]

Bases: py_trees.decorators.Decorator

Be positive, always succeed.

update()[source]

Return the decorated child’s status unless it is FAILURE in which case, return SUCCESS.

Returns:the behaviour’s new status Status
Return type:Status
class py_trees.decorators.Inverter(child, name=<Name.AUTO_GENERATED: 'AUTO_GENERATED'>)[source]

Bases: py_trees.decorators.Decorator

A decorator that inverts the result of a class’s update function.

update()[source]

Flip FAILURE and SUCCESS

Returns:the behaviour’s new status Status
Return type:Status
class py_trees.decorators.OneShot(child, name=<Name.AUTO_GENERATED: 'AUTO_GENERATED'>, policy=<OneShotPolicy.ON_SUCCESSFUL_COMPLETION: [<Status.SUCCESS: 'SUCCESS'>]>)[source]

Bases: py_trees.decorators.Decorator

A decorator that implements the oneshot pattern.

This decorator ensures that the underlying child is ticked through to completion just once and while doing so, will return with the same status as it’s child. Thereafter it will return with the final status of the underlying child.

Completion status is determined by the policy given on construction.

  • With policy ON_SUCCESSFUL_COMPLETION, the oneshot will activate only when the underlying child returns SUCCESS (i.e. it permits retries).
  • With policy ON_COMPLETION, the oneshot will activate when the child returns SUCCESS || FAILURE.
terminate(new_status)[source]

If returning SUCCESS for the first time, flag it so future ticks will block entry to the child.

tick()[source]

Select between decorator (single child) and behaviour (no children) style ticks depending on whether or not the underlying child has been ticked successfully to completion previously.

update()[source]

Bounce if the child has already successfully completed.

class py_trees.decorators.RunningIsFailure(child, name=<Name.AUTO_GENERATED: 'AUTO_GENERATED'>)[source]

Bases: py_trees.decorators.Decorator

Got to be snappy! We want results…yesterday!

update()[source]

Return the decorated child’s status unless it is RUNNING in which case, return FAILURE.

Returns:the behaviour’s new status Status
Return type:Status
class py_trees.decorators.RunningIsSuccess(child, name=<Name.AUTO_GENERATED: 'AUTO_GENERATED'>)[source]

Bases: py_trees.decorators.Decorator

Don’t hang around…

update()[source]

Return the decorated child’s status unless it is RUNNING in which case, return SUCCESS.

Returns:the behaviour’s new status Status
Return type:Status
class py_trees.decorators.StatusToBlackboard(*, child, variable_name, name=<Name.AUTO_GENERATED: 'AUTO_GENERATED'>)[source]

Bases: py_trees.decorators.Decorator

Reflect the status of the decorator’s child to the blackboard.

Parameters:
  • child (Behaviour) – the child behaviour or subtree
  • variable_name (str) – name of the blackboard variable, may be nested, e.g. foo.status
  • name (Union[str, Name]) – the decorator name
update()[source]

Reflect the decorated child’s status to the blackboard and return

Returns: the decorated child’s status

class py_trees.decorators.SuccessIsFailure(child, name=<Name.AUTO_GENERATED: 'AUTO_GENERATED'>)[source]

Bases: py_trees.decorators.Decorator

Be depressed, always fail.

update()[source]

Return the decorated child’s status unless it is SUCCESS in which case, return FAILURE.

Returns:the behaviour’s new status Status
Return type:Status
class py_trees.decorators.SuccessIsRunning(child, name=<Name.AUTO_GENERATED: 'AUTO_GENERATED'>)[source]

Bases: py_trees.decorators.Decorator

It never ends…

update()[source]

Return the decorated child’s status unless it is SUCCESS in which case, return RUNNING.

Returns:the behaviour’s new status Status
Return type:Status
class py_trees.decorators.Timeout(child, name=<Name.AUTO_GENERATED: 'AUTO_GENERATED'>, duration=5.0)[source]

Bases: py_trees.decorators.Decorator

A decorator that applies a timeout pattern to an existing behaviour. If the timeout is reached, the encapsulated behaviour’s stop() method is called with status FAILURE otherwise it will simply directly tick and return with the same status as that of it’s encapsulated behaviour.

initialise()[source]

Reset the feedback message and finish time on behaviour entry.

update()[source]

Terminate the child and return FAILURE if the timeout is exceeded.

py_trees.display

Behaviour trees are significantly easier to design, monitor and debug with visualisations. Py Trees does provide minimal assistance to render trees to various simple output formats. Currently this includes dot graphs, strings or stdout.

py_trees.display.ascii_blackboard(key_filter=None, regex_filter=None, client_filter=None, keys_to_highlight=[], display_only_key_metadata=False, indent=0)[source]

Graffiti your console with ascii art for your blackboard.

Parameters:
  • key_filter (Union[Set[str], List[str], None]) – filter on a set/list of blackboard keys
  • regex_filter (Optional[str]) – filter on a python regex str
  • client_filter (Union[Set[UUID], List[UUID], None]) – filter on a set/list of client uuids
  • keys_to_highlight (List[str]) – list of keys to highlight
  • display_only_key_metadata (bool) – read/write access, … instead of values
  • indent (int) – the number of characters to indent the blackboard
Return type:

str

Returns:

a unicoded blackboard (i.e. in string form)

Note

registered variables that have not yet been set are marked with a ‘-‘

py_trees.display.ascii_symbols = {'space': ' ', 'left_arrow': '<-', 'right_arrow': '->', 'left_right_arrow': '<->', 'bold': '', 'bold_reset': '', 'memory': 'M', 'sequence_with_memory': '{-}', 'selector_with_memory': '{o}', <class 'py_trees.composites.Sequence'>: '[-]', <class 'py_trees.composites.Selector'>: '[o]', <class 'py_trees.composites.Parallel'>: '/_/', <class 'py_trees.decorators.Decorator'>: '-^-', <class 'py_trees.behaviour.Behaviour'>: '-->', <Status.SUCCESS: 'SUCCESS'>: 'o', <Status.FAILURE: 'FAILURE'>: 'x', <Status.INVALID: 'INVALID'>: '-', <Status.RUNNING: 'RUNNING'>: '*'}

Symbols for a non-unicode, non-escape sequence capable console.

py_trees.display.ascii_tree(root, show_only_visited=False, show_status=False, visited={}, previously_visited={}, indent=0)[source]

Graffiti your console with ascii art for your trees.

Parameters:
  • root (Behaviour) – the root of the tree, or subtree you want to show
  • show_only_visited (bool) – show only visited behaviours
  • show_status (bool) – always show status and feedback message (i.e. for every element, not just those visited)
  • visited (dict) – dictionary of (uuid.UUID) and status (Status) pairs for behaviours visited on the current tick
  • previously_visited (dict) – dictionary of behaviour id/status pairs from the previous tree tick
  • indent (int) – the number of characters to indent the tree
Returns:

an ascii tree (i.e. in string form)

Return type:

str

Examples

Use the SnapshotVisitor and BehaviourTree to generate snapshot information at each tick and feed that to a post tick handler that will print the traversed ascii tree complete with status and feedback messages.

_images/ascii_tree.png
def post_tick_handler(snapshot_visitor, behaviour_tree):
    print(
        py_trees.display.unicode_tree(
            behaviour_tree.root,
            visited=snapshot_visitor.visited,
            previously_visited=snapshot_visitor.visited
        )
    )

root = py_trees.composites.Sequence("Sequence")
for action in ["Action 1", "Action 2", "Action 3"]:
    b = py_trees.behaviours.Count(
            name=action,
            fail_until=0,
            running_until=1,
            success_until=10)
    root.add_child(b)
behaviour_tree = py_trees.trees.BehaviourTree(root)
snapshot_visitor = py_trees.visitors.SnapshotVisitor()
behaviour_tree.add_post_tick_handler(
    functools.partial(post_tick_handler,
                      snapshot_visitor))
behaviour_tree.visitors.append(snapshot_visitor)
py_trees.display.dot_tree(root, visibility_level=<VisibilityLevel.DETAIL: 1>, collapse_decorators=False, with_blackboard_variables=False, with_qualified_names=False)[source]

Paint your tree on a pydot graph.

See also

render_dot_tree().

Parameters:
  • root (Behaviour) – the root of a tree, or subtree
  • visibility_level (optional) – collapse subtrees at or under this level
  • collapse_decorators (optional) – only show the decorator (not the child), defaults to False
  • with_blackboard_variables (optional) – add nodes for the blackboard variables
  • with_qualified_names (optional) – print the class information for each behaviour in each node, defaults to False
Returns:

graph

Return type:

pydot.Dot

Examples

# convert the pydot graph to a string object
print("{}".format(py_trees.display.dot_graph(root).to_string()))
py_trees.display.render_dot_tree(root, visibility_level=<VisibilityLevel.DETAIL: 1>, collapse_decorators=False, name=None, target_directory='/home/docs/checkouts/readthedocs.org/user_builds/py-trees/checkouts/release-2.1.x/doc', with_blackboard_variables=False, with_qualified_names=False)[source]

Render the dot tree to .dot, .svg, .png. files in the current working directory. These will be named with the root behaviour name.

Parameters:
  • root (Behaviour) – the root of a tree, or subtree
  • visibility_level (VisibilityLevel) – collapse subtrees at or under this level
  • collapse_decorators (bool) – only show the decorator (not the child)
  • name (Optional[str]) – name to use for the created files (defaults to the root behaviour name)
  • target_directory (str) – default is to use the current working directory, set this to redirect elsewhere
  • with_blackboard_variables (bool) – add nodes for the blackboard variables
  • with_qualified_names (bool) – print the class names of each behaviour in the dot node

Example

Render a simple tree to dot/svg/png file:

digraph sequence {
graph [fontname="times-roman"];
node [fontname="times-roman"];
edge [fontname="times-roman"];
Sequence [fillcolor=orange, fontcolor=black, fontsize=11, shape=box, style=filled];
Guard [fillcolor=gray, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
Sequence -> Guard;
"Action 1" [fillcolor=gray, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
Sequence -> "Action 1";
"Action 2" [fillcolor=gray, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
Sequence -> "Action 2";
"Action 3" [fillcolor=gray, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
Sequence -> "Action 3";
}
root = py_trees.composites.Sequence("Sequence")
for job in ["Action 1", "Action 2", "Action 3"]:
    success_after_two = py_trees.behaviours.Count(name=job,
                                                  fail_until=0,
                                                  running_until=1,
                                                  success_until=10)
    root.add_child(success_after_two)
py_trees.display.render_dot_tree(root)

Tip

A good practice is to provide a command line argument for optional rendering of a program so users can quickly visualise what tree the program will execute.

py_trees.display.unicode_blackboard(key_filter=None, regex_filter=None, client_filter=None, keys_to_highlight=[], display_only_key_metadata=False, indent=0)[source]

Graffiti your console with unicode art for your blackboard.

Parameters:
  • key_filter (Union[Set[str], List[str], None]) – filter on a set/list of blackboard keys
  • regex_filter (Optional[str]) – filter on a python regex str
  • client_filter (Union[Set[UUID], List[UUID], None]) – filter on a set/list of client uuids
  • keys_to_highlight (List[str]) – list of keys to highlight
  • display_only_key_metadata (bool) – read/write access, … instead of values
  • indent (int) – the number of characters to indent the blackboard
Return type:

str

Returns:

a unicoded blackboard (i.e. in string form)

Note

registered variables that have not yet been set are marked with a ‘-‘

py_trees.display.unicode_blackboard_activity_stream(activity_stream=None, indent=0, show_title=True)[source]

Pretty print the blackboard stream to console.

Parameters:
  • activity_stream (Optional[List[ActivityItem]]) – the log of activity, if None, get the entire activity stream
  • indent (int) – the number of characters to indent the blackboard
  • show_title (bool) – include the title in the output
py_trees.display.unicode_symbols = {'space': ' ', 'left_arrow': '←', 'right_arrow': '→', 'left_right_arrow': '↔', 'bold': '', 'bold_reset': '', 'memory': 'Ⓜ', 'sequence_with_memory': '{-}', 'selector_with_memory': '{o}', <class 'py_trees.composites.Sequence'>: '[-]', <class 'py_trees.composites.Selector'>: '[o]', <class 'py_trees.composites.Parallel'>: '/_/', <class 'py_trees.decorators.Decorator'>: '-^-', <class 'py_trees.behaviour.Behaviour'>: '-->', <Status.SUCCESS: 'SUCCESS'>: '✓', <Status.FAILURE: 'FAILURE'>: '✕', <Status.INVALID: 'INVALID'>: '-', <Status.RUNNING: 'RUNNING'>: '*'}

Symbols for a unicode, escape sequence capable console.

py_trees.display.unicode_tree(root, show_only_visited=False, show_status=False, visited={}, previously_visited={}, indent=0)[source]

Graffiti your console with unicode art for your trees.

Parameters:
  • root (Behaviour) – the root of the tree, or subtree you want to show
  • show_only_visited (bool) – show only visited behaviours
  • show_status (bool) – always show status and feedback message (i.e. for every element, not just those visited)
  • visited (dict) – dictionary of (uuid.UUID) and status (Status) pairs for behaviours visited on the current tick
  • previously_visited (dict) – dictionary of behaviour id/status pairs from the previous tree tick
  • indent (int) – the number of characters to indent the tree
Returns:

a unicode tree (i.e. in string form)

Return type:

str

py_trees.display.xhtml_symbols = {'space': '<text>&#xa0;</text>', 'left_arrow': '<text>&#x2190;</text>', 'right_arrow': '<text>&#x2192;</text>', 'left_right_arrow': '<text>&#x2194;</text>', 'bold': '<b>', 'bold_reset': '</b>', 'memory': '<text>&#x24C2;</text>', 'sequence_with_memory': '<text>{-}</text>', 'selector_with_memory': '<text>{o}</text>', <class 'py_trees.composites.Sequence'>: '<text>[-]</text>', <class 'py_trees.composites.Selector'>: '<text>[o]</text>', <class 'py_trees.composites.Parallel'>: '<text style="color:green;">/_/</text>', <class 'py_trees.decorators.Decorator'>: '<text>-^-</text>', <class 'py_trees.behaviour.Behaviour'>: '<text>--></text>', <Status.SUCCESS: 'SUCCESS'>: '<text style="color:green;">&#x2713;</text>', <Status.FAILURE: 'FAILURE'>: '<text style="color:red;">&#x2715;</text>', <Status.INVALID: 'INVALID'>: '<text style="color:darkgoldenrod;">-</text>', <Status.RUNNING: 'RUNNING'>: '<text style="color:blue;">*</text>'}

Symbols for embedding in html.

py_trees.display.xhtml_tree(root, show_only_visited=False, show_status=False, visited={}, previously_visited={}, indent=0)[source]

Paint your tree on an xhtml snippet.

Parameters:
  • root (Behaviour) – the root of the tree, or subtree you want to show
  • show_only_visited (bool) – show only visited behaviours
  • show_status (bool) – always show status and feedback message (i.e. for every element, not just those visited)
  • visited (dict) – dictionary of (uuid.UUID) and status (Status) pairs for behaviours visited on the current tick
  • previously_visited (dict) – dictionary of behaviour id/status pairs from the previous tree tick
  • indent (int) – the number of characters to indent the tree
Returns:

an ascii tree (i.e. as a xhtml snippet)

Return type:

str

Examples:

import py_trees
a = py_trees.behaviours.Success()
b = py_trees.behaviours.Success()
c = c = py_trees.composites.Sequence(children=[a, b])
c.tick_once()

f = open('testies.html', 'w')
f.write('<html><head><title>Foo</title><body>')
f.write(py_trees.display.xhtml_tree(c, show_status=True))
f.write("</body></html>")

py_trees.idioms

A library of subtree creators that build complex patterns of behaviours representing common behaviour tree idioms.

py_trees.idioms.either_or(conditions, subtrees, name='Either Or', namespace=None)[source]

Often you need a kind of selector that doesn’t implement prioritisations, i.e. you would like different paths to be selected on a first-come, first-served basis.

task_one = py_trees.behaviours.TickCounter(name="Subtree 1", duration=2)
task_two = py_trees.behaviours.TickCounter(name="Subtree 2", duration=2)
either_or = py_trees.idioms.either_or(
    name="EitherOr",
    conditions=[
        py_trees.common.ComparisonExpression("joystick_one", "enabled", operator.eq),
        py_trees.common.ComparisonExpression("joystick_two", "enabled", operator.eq),
    ],
    subtrees=[task_one, task_two],
    namespace="either_or",
)
digraph pastafarianism {
ordering=out;
graph [fontname="times-roman"];
node [fontname="times-roman"];
edge [fontname="times-roman"];
EitherOr [label=EitherOr, shape=box, style=filled, fillcolor=orange, fontsize=9, fontcolor=black];
XOR [label=XOR, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
EitherOr -> XOR;
Chooser [label=Chooser, shape=octagon, style=filled, fillcolor=cyan, fontsize=9, fontcolor=black];
EitherOr -> Chooser;
"Option 1" [label="Option 1", shape=box, style=filled, fillcolor=orange, fontsize=9, fontcolor=black];
Chooser -> "Option 1";
"Enabled?" [label="Enabled?", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
"Option 1" -> "Enabled?";
"Subtree 1" [label="Subtree 1", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
"Option 1" -> "Subtree 1";
"Option 2" [label="Option 2", shape=box, style=filled, fillcolor=orange, fontsize=9, fontcolor=black];
Chooser -> "Option 2";
"Enabled?*" [label="Enabled?*", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
"Option 2" -> "Enabled?*";
"Subtree 2" [label="Subtree 2", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
"Option 2" -> "Subtree 2";
}

Idiom - Either Or

Up front is an XOR conditional check which locks in the result on the blackboard under the specified namespace. Locking the result in permits the conditional variables to vary in future ticks without interrupting the execution of the chosen subtree (an example of a conditional variable may be one that has registered joystick button presses).

Once the result is locked in, the relevant subtree is activated beneath the selector. The children of the selector are, from left to right, not in any order of priority since the previous xor choice has been locked in and isn’t revisited until the subtree executes to completion. Only one may be active and it cannot be interrupted by the others.

The only means of interrupting the execution is via a higher priority in the tree that this idiom is embedded in.

Parameters:
  • conditions (List[ComparisonExpression]) – list of triggers that ultimately select the subtree to enable
  • subtrees (List[Behaviour]) – list of subtrees to tick from in the either_or operation
  • name – the name to use for this idiom’s root behaviour
  • preemptible – whether the subtrees may preempt (interrupt) each other
  • namespace (Optional[str]) – this idiom’s private variables will be put behind this namespace
Raises:

ValueError if the number of conditions does not match the number of subtrees

If no namespace is provided, a unique one is derived from the idiom’s name.

Todo

a version for which other subtrees can preempt (in an unprioritised manner) the active branch

Return type:Behaviour
py_trees.idioms.eternal_guard(subtree, name='Eternal Guard', conditions=[], blackboard_namespace=None)[source]

The eternal guard idiom implements a stronger guard than the typical check at the beginning of a sequence of tasks. Here they guard continuously while the task sequence is being executed. While executing, if any of the guards should update with status FAILURE, then the task sequence is terminated.

digraph pastafarianism {
graph [fontname="times-roman"];
node [fontname="times-roman"];
edge [fontname="times-roman"];
"Eternal Guard" [fillcolor=gold, fontcolor=black, fontsize=9, label="Eternal Guard\n--SuccessOnAll(-)--", shape=parallelogram, style=filled];
StatusToBB [fillcolor=ghostwhite, fontcolor=black, fontsize=9, label=StatusToBB, shape=ellipse, style=filled];
"Eternal Guard" -> StatusToBB;
"Condition 1" [fillcolor=gray, fontcolor=black, fontsize=9, label="Condition 1", shape=ellipse, style=filled];
StatusToBB -> "Condition 1";
"StatusToBB*" [fillcolor=ghostwhite, fontcolor=black, fontsize=9, label="StatusToBB*", shape=ellipse, style=filled];
"Eternal Guard" -> "StatusToBB*";
"Condition 2" [fillcolor=gray, fontcolor=black, fontsize=9, label="Condition 2", shape=ellipse, style=filled];
"StatusToBB*" -> "Condition 2";
"Guarded Tasks" [fillcolor=cyan, fontcolor=black, fontsize=9, label="Guarded Tasks", shape=octagon, style=filled];
"Eternal Guard" -> "Guarded Tasks";
"Abort on\nCondition 1" [fillcolor=gray, fontcolor=black, fontsize=9, label="Abort on\nCondition 1", shape=ellipse, style=filled];
"Guarded Tasks" -> "Abort on\nCondition 1";
"Abort on\nCondition 2" [fillcolor=gray, fontcolor=black, fontsize=9, label="Abort on\nCondition 2", shape=ellipse, style=filled];
"Guarded Tasks" -> "Abort on\nCondition 2";
Tasks [fillcolor=orange, fontcolor=black, fontsize=9, label=Tasks, shape=box, style=filled];
"Guarded Tasks" -> Tasks;
Task1 [fillcolor=gray, fontcolor=black, fontsize=9, label=Task1, shape=ellipse, style=filled];
Tasks -> Task1;
Task2 [fillcolor=gray, fontcolor=black, fontsize=9, label=Task2, shape=ellipse, style=filled];
Tasks -> Task2;
}
Parameters:
  • subtree (Behaviour) – behaviour(s) that actually do the work
  • name (str) – the name to use on the root behaviour of the idiom subtree
  • conditions (List[Behaviour]) – behaviours on which tasks are conditional
  • blackboard_namespace (Optional[str]) – applied to condition variable results stored on the blackboard (default: derived from the idiom name)
Return type:

Behaviour

Returns:

the root of the idiom subtree

py_trees.idioms.oneshot(behaviour, name='Oneshot', variable_name='oneshot', policy=<OneShotPolicy.ON_SUCCESSFUL_COMPLETION: [<Status.SUCCESS: 'SUCCESS'>]>)[source]

Ensure that a particular pattern is executed through to completion just once. Thereafter it will just rebound with the completion status.

digraph oneshot {
graph [fontname="times-roman"];
node [fontname="times-roman"];
edge [fontname="times-roman"];
OneShot [fillcolor=cyan, fontcolor=black, fontsize=11, shape=octagon, style=filled];
"Oneshot w/ Guard" [fillcolor=orange, fontcolor=black, fontsize=11, shape=box, style=filled];
OneShot -> "Oneshot w/ Guard";
"Not Completed?" [fillcolor=ghostwhite, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
"Oneshot w/ Guard" -> "Not Completed?";
"Completed?" [fillcolor=gray, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
"Not Completed?" -> "Completed?";
Sequence [fillcolor=orange, fontcolor=black, fontsize=11, shape=box, style=filled];
"Oneshot w/ Guard" -> Sequence;
Guard [fillcolor=gray, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
Sequence -> Guard;
"Action 1" [fillcolor=gray, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
Sequence -> "Action 1";
"Action 2" [fillcolor=gray, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
Sequence -> "Action 2";
"Action 3" [fillcolor=gray, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
Sequence -> "Action 3";
"Mark Done\n[SUCCESS]" [fillcolor=gray, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
Sequence -> "Mark Done\n[SUCCESS]";
"Oneshot Result" [fillcolor=gray, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
OneShot -> "Oneshot Result";
}

Note

Set the policy to configure the oneshot to keep trying if failing, or to abort further attempts regardless of whether it finished with status FAILURE.

Parameters:
  • behaviour (Behaviour) – single behaviour or composited subtree to oneshot
  • name (str) – the name to use for the oneshot root (selector)
  • variable_name (str) – name for the variable used on the blackboard, may be nested
  • policy (OneShotPolicy) – execute just once regardless of success or failure, or keep trying if failing
Returns:

the root of the oneshot subtree

Return type:

Behaviour

py_trees.idioms.pick_up_where_you_left_off(name='Pickup Where You Left Off Idiom', tasks=[])[source]

Rudely interrupted while enjoying a sandwich, a caveman (just because they wore loincloths does not mean they were not civilised), picks up his club and fends off the sabre-tooth tiger invading his sanctum as if he were swatting away a gnat. Task accomplished, he returns to the joys of munching through the layers of his sandwich.

digraph pick_up_where_you_left_off {
graph [fontname="times-roman"];
node [fontname="times-roman"];
edge [fontname="times-roman"];
"Pick Up Where You Left Off" [shape=octagon, style=filled, fillcolor=cyan, fontsize=11, fontcolor=black];
"High Priority" [shape=ellipse, style=filled, fillcolor=gray, fontsize=11, fontcolor=black];
"Pick Up Where You Left Off" -> "High Priority";
Tasks [shape=box, style=filled, fillcolor=orange, fontsize=11, fontcolor=black];
"Pick Up Where You Left Off" -> Tasks;
"Do or Don't" [shape=octagon, style=filled, fillcolor=cyan, fontsize=11, fontcolor=black];
Tasks -> "Do or Don't";
"Done?" [shape=ellipse, style=filled, fillcolor=gray, fontsize=11, fontcolor=black];
"Do or Don't" -> "Done?";
Worker [shape=box, style=filled, fillcolor=orange, fontsize=11, fontcolor=black];
"Do or Don't" -> Worker;
"Task 1" [shape=ellipse, style=filled, fillcolor=gray, fontsize=11, fontcolor=black];
Worker -> "Task 1";
"Mark\ntask_1_done" [shape=ellipse, style=filled, fillcolor=gray, fontsize=11, fontcolor=black];
Worker -> "Mark\ntask_1_done";
"Do or Don't*" [shape=octagon, style=filled, fillcolor=cyan, fontsize=11, fontcolor=black];
Tasks -> "Do or Don't*";
"Done?*" [shape=ellipse, style=filled, fillcolor=gray, fontsize=11, fontcolor=black];
"Do or Don't*" -> "Done?*";
"Worker*" [shape=box, style=filled, fillcolor=orange, fontsize=11, fontcolor=black];
"Do or Don't*" -> "Worker*";
"Task 2" [shape=ellipse, style=filled, fillcolor=gray, fontsize=11, fontcolor=black];
"Worker*" -> "Task 2";
"Mark\ntask_2_done" [shape=ellipse, style=filled, fillcolor=gray, fontsize=11, fontcolor=black];
"Worker*" -> "Mark\ntask_2_done";
"Clear\ntask_1_done" [shape=ellipse, style=filled, fillcolor=gray, fontsize=11, fontcolor=black];
Tasks -> "Clear\ntask_1_done";
"Clear\ntask_2_done" [shape=ellipse, style=filled, fillcolor=gray, fontsize=11, fontcolor=black];
Tasks -> "Clear\ntask_2_done";
}

Note

There are alternative ways to accomplish this idiom with their pros and cons.

a) The tasks in the sequence could be replaced by a factory behaviour that dynamically checks the state of play and spins up the tasks required each time the task sequence is first entered and invalidates/deletes them when it is either finished or invalidated. That has the advantage of not requiring much of the blackboard machinery here, but disadvantage in not making visible the task sequence itself at all times (i.e. burying details under the hood).

b) A new composite which retains the index between initialisations can also achieve the same pattern with fewer blackboard shenanigans, but suffers from an increased logical complexity cost for your trees (each new composite increases decision making complexity (O(n!)).

Parameters:
  • name (str) – the name to use for the task sequence behaviour
  • tasks ([Behaviour) – lists of tasks to be sequentially performed
Returns:

root of the generated subtree

Return type:

Behaviour

py_trees.meta

Meta methods to create behaviours without needing to create the behaviours themselves.

py_trees.meta.create_behaviour_from_function(func)[source]

Create a behaviour from the specified function, dropping it in for the Behaviour update() method. Ths function must include the self argument and return a Status value. It also automatically provides a drop-in for the terminate() method that clears the feedback message. Other methods are left untouched.

Parameters:func (function) – a drop-in for the update() method

py_trees.timers

Time related behaviours.

class py_trees.timers.Timer(name='Timer', duration=5.0)[source]

Bases: py_trees.behaviour.Behaviour

Simple timer class that is RUNNING until the timer runs out, at which point it is SUCCESS. This can be used in a wide variety of situations - pause, duration, timeout depending on how it is wired into the tree (e.g. pause in a sequence, duration/timeout in a parallel).

The timer gets reset either upon entry (initialise()) if it hasn’t already been set and gets cleared when it either runs out, or the behaviour is interrupted by a higher priority or parent cancelling it.

Parameters:
  • name (str) – name of the behaviour
  • duration (int) – length of time to run (in seconds)
Raises:

TypeError – if the provided duration is not a real number

Note

This succeeds the first time the behaviour is ticked after the expected finishing time.

Tip

Use the RunningIsFailure() decorator if you need FAILURE until the timer finishes.

__init__(name='Timer', duration=5.0)[source]

Initialize self. See help(type(self)) for accurate signature.

initialise()[source]

Store the expected finishing time.

terminate(new_status)[source]

Clear the expected finishing time.

update()[source]

Check current time against the expected finishing time. If it is in excess, flip to SUCCESS.

py_trees.trees

While a graph of connected behaviours and composites form a tree in their own right (i.e. it can be initialised and ticked), it is usually convenient to wrap your tree in another class to take care of alot of the housework and provide some extra bells and whistles that make your tree flourish.

_images/yggdrasil.jpg

This package provides a default reference implementation that is directly usable, but can also be easily used as inspiration for your own tree custodians.

class py_trees.trees.BehaviourTree(root)[source]

Bases: object

Grow, water, prune your behaviour tree with this, the default reference implementation. It features a few enhancements to provide richer logging, introspection and dynamic management of the tree itself:

  • Pre and post tick handlers to execute code automatically before and after a tick
  • Visitor access to the parts of the tree that were traversed in a tick
  • Subtree pruning and insertion operations
  • Continuous tick-tock support

See also

The py-trees-demo-tree-stewardship program demonstrates the above features.

Parameters:

root (Behaviour) – root node of the tree

Variables:
  • count – number of times the tree has been ticked.
  • root – root node of the tree
  • visitors – entities that visit traversed parts of the tree when it ticks
  • pre_tick_handlers – functions that run before the entire tree is ticked
  • post_tick_handlers – functions that run after the entire tree is ticked
Raises:

TypeError – if root variable is not an instance of Behaviour

add_post_tick_handler(handler)[source]

Add a function to execute after the tree has ticked. The function must have a single argument of type BehaviourTree.

Some ideas that are often used:

  • logging
  • modifications on the tree itself (e.g. closing down a plan)
  • sending data to visualisation tools
  • introspect the state of the tree to make and send reports
Parameters:handler (func) – function
add_pre_tick_handler(handler)[source]

Add a function to execute before the tree is ticked. The function must have a single argument of type BehaviourTree.

Some ideas that are often used:

  • logging (to file or stdout)
  • modifications on the tree itself (e.g. starting a new plan)
Parameters:handler (func) – function
add_visitor(visitor)[source]

Trees can run multiple visitors on each behaviour as they tick through a tree.

Parameters:visitor (VisitorBase) – sub-classed instance of a visitor
insert_subtree(child, unique_id, index)[source]

Insert a subtree as a child of the specified parent. If the parent is found, this directly calls the parent’s insert_child() method using the child and index arguments.

Parameters:
  • child (Behaviour) – subtree to insert
  • unique_id (uuid.UUID) – unique id of the parent
  • index (int) – insert the child at this index, pushing all children after it back one.
Returns:

suceess or failure (parent not found) of the operation

Return type:

bool

Raises:

TypeError – if the parent is not a Composite

Todo

Could use better, more informative error handling here. Especially if the insertion has its own error handling (e.g. index out of range). Could also use a different api that relies on the id of the sibling node it should be inserted before/after.

interrupt()[source]

Interrupt tick-tock if it is tick-tocking. Note that this will permit a currently executing tick to finish before interrupting the tick-tock.

prune_subtree(unique_id)[source]

Prune a subtree given the unique id of the root of the subtree.

Parameters:unique_id (uuid.UUID) – unique id of the subtree root
Returns:success or failure of the operation
Return type:bool
Raises:RuntimeError – if unique id is the behaviour tree’s root node id
replace_subtree(unique_id, subtree)[source]

Replace the subtree with the specified id for the new subtree. This is a common pattern where we’d like to swap out a whole sub-behaviour for another one.

Parameters:
  • unique_id (uuid.UUID) – unique id of the parent
  • subtree (Behaviour) – root behaviour of the subtree
Raises
AssertionError: if unique id is the behaviour tree’s root node id
Returns:suceess or failure of the operation
Return type:bool
setup(timeout=<Duration.INFINITE: inf>, visitor=None, **kwargs)[source]

Crawls across the tree calling setup() on each behaviour.

Visitors can optionally be provided to provide a node-by-node analysis on the result of each node’s setup() before the next node’s setup() is called. This is useful on trees with relatively long setup times to progressively report out on the current status of the operation.

Parameters:
  • timeout (float) – time (s) to wait (use common.Duration.INFINITE to block indefinitely)
  • visitor (VisitorBase) – runnable entities on each node after it’s setup
  • **kwargs (dict) – distribute arguments to this behaviour and in turn, all of it’s children
Raises:
  • Exception – be ready to catch if any of the behaviours raise an exception
  • RuntimeError – in case setup() times out
shutdown()[source]

Crawls across the tree calling shutdown() on each behaviour.

Raises:Exception – be ready to catch if any of the behaviours raise an exception
tick(pre_tick_handler=None, post_tick_handler=None)[source]

Tick the tree just once and run any handlers before and after the tick. This optionally accepts some one-shot handlers (c.f. those added by add_pre_tick_handler() and add_post_tick_handler() which will be automatically run every time).

The handler functions must have a single argument of type BehaviourTree.

Parameters:
  • pre_tick_handler (func) – function to execute before ticking
  • post_tick_handler (func) – function to execute after ticking
tick_tock(period_ms, number_of_iterations=-1, pre_tick_handler=None, post_tick_handler=None)[source]

Tick continuously with period as specified. Depending on the implementation, the period may be more or less accurate and may drift in some cases (the default implementation here merely assumes zero time in tick and sleeps for this duration of time and consequently, will drift).

This optionally accepts some handlers that will be used for the duration of this tick tock (c.f. those added by add_pre_tick_handler() and add_post_tick_handler() which will be automatically run every time).

The handler functions must have a single argument of type BehaviourTree.

Parameters:
  • period_ms (float) – sleep this much between ticks (milliseconds)
  • number_of_iterations (int) – number of iterations to tick-tock
  • pre_tick_handler (func) – function to execute before ticking
  • post_tick_handler (func) – function to execute after ticking
tip()[source]

Get the tip of the tree. This corresponds to the the deepest node that was running before the subtree traversal reversed direction and headed back to this node.

Returns:child behaviour, itself or None if its status is INVALID
Return type:Behaviour or None

See also

tip()

py_trees.trees.setup(root, timeout=<Duration.INFINITE: inf>, visitor=None, **kwargs)[source]

Crawls across a (sub)tree of behaviours calling setup() on each behaviour.

Visitors can optionally be provided to provide a node-by-node analysis on the result of each node’s setup() before the next node’s setup() is called. This is useful on trees with relatively long setup times to progressively report out on the current status of the operation.

Parameters:
  • root (Behaviour) – unmanaged (sub)tree root behaviour
  • timeout (Union[float, Duration]) – time (s) to wait (use common.Duration.INFINITE to block indefinitely)
  • visitor (Optional[VisitorBase]) – runnable entities on each node after it’s setup
  • **kwargs – dictionary of arguments to distribute to all behaviours in the (sub) tree
Raises:
  • Exception – be ready to catch if any of the behaviours raise an exception
  • RuntimeError – in case setup() times out

py_trees.utilities

Assorted utility functions.

class py_trees.utilities.Process(*args, **kwargs)[source]

Bases: multiprocessing.context.Process

run()[source]

Method to be run in sub-process; can be overridden in sub-class

py_trees.utilities.get_fully_qualified_name(instance)[source]

Get at the fully qualified name of an object, e.g. an instance of a Sequence becomes ‘py_trees.composites.Sequence’.

Parameters:instance (object) – an instance of any class
Returns:the fully qualified name
Return type:str
py_trees.utilities.get_valid_filename(s)[source]

Return the given string converted to a string that can be used for a clean filename (without extension). Remove leading and trailing spaces; convert other spaces and newlines to underscores; and remove anything that is not an alphanumeric, dash, underscore, or dot.

>>> utilities.get_valid_filename("john's portrait in 2004.jpg")
'johns_portrait_in_2004.jpg'
Parameters:program (str) – string to convert to a valid filename
Returns:a representation of the specified string as a valid filename
Return type:str
py_trees.utilities.is_primitive(incoming)[source]

Check if an incoming argument is a primitive type with no esoteric accessors (e.g. class attributes or container [] accessors.

Parameters:incoming (Any) – the instance to check
Return type:bool
Returns:True or false, depending on the check against the reserved primitives
py_trees.utilities.static_variables(**kwargs)[source]

This is a decorator that can be used with python methods to attach initialised static variables to the method.

@static_variables(counter=0)
def foo():
    foo.counter += 1
    print("Counter: {}".format(foo.counter))
py_trees.utilities.truncate(original, length)[source]

Provide an elided version of the string for which the last three characters are dots if the original string does not fit within the specified length.

Parameters:
  • original (str) – string to elide
  • length (int) – constrain the elided string to this
Return type:

str

py_trees.utilities.which(program)[source]

Wrapper around the command line ‘which’ program.

Parameters:program (str) – name of the program to find.
Returns:path to the program or None if it doesnt exist.
Return type:str

py_trees.visitors

Visitors are entities that can be passed to a tree implementation (e.g. BehaviourTree) and used to either visit each and every behaviour in the tree, or visit behaviours as the tree is traversed in an executing tick. At each behaviour, the visitor runs its own method on the behaviour to do as it wishes - logging, introspecting, etc.

Warning

Visitors should not modify the behaviours they visit.

class py_trees.visitors.DebugVisitor[source]

Bases: py_trees.visitors.VisitorBase

Picks up and logs feedback messages and the behaviour’s status. Logging is done with the behaviour’s logger.

run(behaviour)[source]

This method gets run as each behaviour is ticked. Override it to perform some activity - e.g. introspect the behaviour to store/process logging data for visualisations.

Parameters:behaviour (Behaviour) – behaviour that is ticking
class py_trees.visitors.DisplaySnapshotVisitor(display_only_visited_behaviours=False, display_blackboard=False, display_activity_stream=False)[source]

Bases: py_trees.visitors.SnapshotVisitor

Visit the tree, capturing the visited path, it’s changes since the last tick and additionally print the snapshot to console.

Parameters:
  • display_blackboard (bool) – print to the console the relevant part of the blackboard associated with behaviours on the visited path
  • display_activity_stream (bool) – print to the console a log of the activity on the blackboard over the last tick
finalise()[source]

Override this method if any work needs to be performed after ticks (i.e. showing data).

initialise()[source]

Switch running to previously running and then reset all other variables. This should get called before a tree ticks.

run(behaviour)[source]

This method gets run as each behaviour is ticked. Catch the id and status and store it. Additionally add it to the running list if it is RUNNING.

Parameters:behaviour (Behaviour) – behaviour that is ticking
class py_trees.visitors.SnapshotVisitor[source]

Bases: py_trees.visitors.VisitorBase

Visits the ticked part of a tree, checking off the status against the set of status results recorded in the previous tick. If there has been a change, it flags it. This is useful for determining when to trigger, e.g. logging.

Variables:
  • changed (Bool) – flagged if there is a difference in the visited path or Status of any behaviour on the path
  • visited (dict) – dictionary of behaviour id (uuid.UUID) and status (Status) pairs from the current tick
  • previously_visited (dict) – dictionary of behaviour id (uuid.UUID) and status (Status) pairs from the previous tick
  • running_nodes ([uuid.UUID]) – list of id’s for behaviours which were traversed in the current tick
  • previously_running_nodes ([uuid.UUID]) – list of id’s for behaviours which were traversed in the last tick
  • visited_blackboard_ids (typing.Set[uuid.UUID]) – blackboard client id’s on the visited path
  • visited_blackboard_keys (typing.Set[str]) – blackboard variable keys on the visited path

See also

The py-trees-demo-logging program demonstrates use of this visitor to trigger logging of a tree serialisation.

initialise()[source]

Switch running to previously running and then reset all other variables. This should get called before a tree ticks.

run(behaviour)[source]

This method gets run as each behaviour is ticked. Catch the id and status and store it. Additionally add it to the running list if it is RUNNING.

Parameters:behaviour (Behaviour) – behaviour that is ticking
class py_trees.visitors.VisitorBase(full=False)[source]

Bases: object

Parent template for visitor types.

Visitors are primarily designed to work with BehaviourTree but they can be used in the same way for other tree custodian implementations.

Parameters:full (bool) – flag to indicate whether it should be used to visit only traversed nodes or the entire tree
Variables:full (bool) – flag to indicate whether it should be used to visit only traversed nodes or the entire tree
finalise()[source]

Override this method if any work needs to be performed after ticks (i.e. showing data).

initialise()[source]

Override this method if any resetting of variables needs to be performed between ticks (i.e. visitations).

run(behaviour)[source]

This method gets run as each behaviour is ticked. Override it to perform some activity - e.g. introspect the behaviour to store/process logging data for visualisations.

Parameters:behaviour (Behaviour) – behaviour that is ticking