#!/usr/bin/env python
#
# License: BSD
# https://raw.githubusercontent.com/stonier/py_trees/devel/LICENSE
#
##############################################################################
# Documentation
##############################################################################
"""
The core behaviour template. All behaviours, standalone and composite, inherit
from this class.
"""
##############################################################################
# Imports
##############################################################################
import re
import uuid
from . import logging
from . import common
from .common import Status
##############################################################################
# Behaviour BluePrint
##############################################################################
[docs]class Behaviour(object):
"""
Defines the basic properties and methods required of a node in a
behaviour tree. When implementing your own behaviour,
subclass this class.
Args:
name (:obj:`str`): the behaviour name
Raises:
TypeError: if the provided name is not a string
Attributes:
id (:class:`uuid.UUID`): automagically generated unique identifier for the behaviour
name (:obj:`str`): the behaviour name
status (:class:`~py_trees.common.Status`): the behaviour status (:data:`~py_trees.common.Status.INVALID`, :data:`~py_trees.common.Status.RUNNING`, :data:`~py_trees.common.Status.FAILURE`, :data:`~py_trees.common.Status.SUCCESS`)
parent (:class:`~py_trees.behaviour.Behaviour`): a :class:`~py_trees.composites.Composite` instance if nested in a tree, otherwise None
children ([:class:`~py_trees.behaviour.Behaviour`]): empty for regular behaviours, populated for composites
logger (:class:`logging.Logger`): a simple logging mechanism
feedback_message(:obj:`str`): a simple message used to notify of significant happenings
blackbox_level (:class:`~py_trees.common.BlackBoxLevel`): a helper variable for dot graphs and runtime gui's to collapse/explode entire subtrees dependent upon the blackbox level.
.. seealso::
* :ref:`Skeleton Behaviour Template <skeleton-behaviour-include>`
* :ref:`The Lifecycle Demo <py-trees-demo-behaviour-lifecycle-program>`
* :ref:`The Action Behaviour Demo <py-trees-demo-action-behaviour-program>`
"""
def __init__(self, name=common.Name.AUTO_GENERATED):
if not name or name == common.Name.AUTO_GENERATED:
name = self.__class__.__name__
if not isinstance(name, str):
raise TypeError("a behaviour name should be a string, but you passed in {}".format(type(name)))
self.id = uuid.uuid4() # used to uniquely identify this node (helps with removing children from a tree)
self.name = name
self.status = Status.INVALID
self.iterator = self.tick()
self.parent = None # will get set if a behaviour is added to a composite
self.children = [] # only set by composite behaviours
self.logger = logging.Logger(name)
self.feedback_message = "" # useful for debugging, or human readable updates, but not necessary to implement
self.blackbox_level = common.BlackBoxLevel.NOT_A_BLACKBOX
############################################
# User Customisable Callbacks
############################################
[docs] def setup(self):
"""
.. note:: User Customisable Callback
Subclasses may override this method to do 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).
Equally as important, executing methods which validate the configuration of
behaviours will help 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.
Raises:
Exception: if this behaviour has a fault in construction or configuration
"""
pass
[docs] def initialise(self):
"""
.. 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
:data:`~py_trees.common.Status.RUNNING`. i.e. Expect this to trigger more than once!
"""
pass
[docs] def terminate(self, new_status):
"""
.. note:: User Customisable Callback
Subclasses may override this method to clean up. It will be triggered when a behaviour either
finishes execution (switching from :data:`~py_trees.common.Status.RUNNING`
to :data:`~py_trees.common.Status.FAILURE` || :data:`~py_trees.common.Status.SUCCESS`)
or it got interrupted by a higher priority branch (switching to
:data:`~py_trees.common.Status.INVALID`). Remember that the :meth:`~py_trees.behaviour.Behaviour.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
Args:
new_status (:class:`~py_trees.common.Status`): the behaviour is transitioning to this new status
.. warning:: Do not set `self.status = new_status` here, that is automatically handled
by the :meth:`~py_trees.behaviour.Behaviour.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`.
"""
pass
[docs] def update(self):
"""
.. note:: User Customisable Callback
Returns:
:class:`~py_trees.common.Status`: the behaviour's new status :class:`~py_trees.common.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 :meth:`~py_trees.behaviour.Behaviour.tick` mechanism.
.. tip:: This method should be almost instantaneous and non-blocking
"""
return Status.INVALID
############################################
# User Methods
############################################
[docs] def tick_once(self):
"""
A direct means of calling tick on this object without
using the generator mechanism.
"""
# no logger necessary here...it directly relays to tick
for unused in self.tick():
pass
[docs] def setup_with_descendants(self):
"""
Iterates over this child, it's children (it's children's children, ...)
calling the user defined :meth:`~py_trees.behaviour.Behaviuor.setup`
on each in turn.
"""
for child in self.children:
for node in child.iterate():
node.setup()
self.setup()
############################################
# Introspection
############################################
[docs] def has_parent_with_name(self, name):
"""
Searches through this behaviour's parents, and their parents, looking for
a behaviour with the same name as that specified.
Args:
name (:obj:`str`): name of the parent to match, can be a regular expression
Returns:
bool: whether a parent was found or not
"""
pattern = re.compile(name)
b = self
while b.parent is not None:
if pattern.match(b.parent.name) is not None:
return True
b = b.parent
return False
[docs] def has_parent_with_instance_type(self, instance_type):
"""
Moves up through this behaviour's parents looking for
a behaviour with the same instance type as that specified.
Args:
instance_type (:obj:`str`): instance type of the parent to match
Returns:
bool: whether a parent was found or not
"""
b = self
while b.parent is not None:
if isinstance(b.parent, instance_type):
return True
b = b.parent
return False
[docs] def tip(self):
"""
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:
:class:`~py_trees.behaviour.Behaviour` or :obj:`None`: child behaviour, itself or :obj:`None` if its status is :data:`~py_trees.common.Status.INVALID`
"""
return self if self.status != Status.INVALID else None
############################################
# Advanced Methods
# (oft indirectly used from a TreeManager)
############################################
[docs] def visit(self, visitor):
"""
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.
Args:
visitor (:obj:`object`): the visiting class, must have a run(:class:`~py_trees.behaviour.Behaviour`) method.
"""
visitor.run(self)
[docs] def tick(self):
"""
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 :meth:`~py_trees.behaviour.Behaviour.initialise` and :meth:`~py_trees.behaviour.Behaviour.terminate` methods as well as making the
actual call to the user's :meth:`~py_trees.behaviour.Behaviour.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.
.. code-block:: python
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 :meth:`~py_trees.behaviour.Behaviour.tick_once` instead.
Yields:
:class:`~py_trees.behaviour.Behaviour`: a reference to itself
.. warning:: Override this method only in exceptional circumstances, prefer overriding :meth:`~py_trees.behaviour.Behaviour.update` instead.
"""
self.logger.debug("%s.tick()" % (self.__class__.__name__))
if self.status != Status.RUNNING:
self.initialise()
# don't set self.status yet, terminate() may need to check what the current state is first
new_status = self.update()
if new_status not in list(Status):
self.logger.error("A behaviour returned an invalid status, setting to INVALID [%s][%s]" % (new_status, self.name))
new_status = Status.INVALID
if new_status != Status.RUNNING:
self.stop(new_status)
self.status = new_status
yield self
[docs] def iterate(self, direct_descendants=False):
"""
Generator that provides iteration over this behaviour and all its children.
To traverse the entire tree:
.. code-block:: python
for node in my_behaviour.iterate():
print("Name: {0}".format(node.name))
Args:
direct_descendants (:obj:`bool`): only yield children one step away from this behaviour.
Yields:
:class:`~py_trees.behaviour.Behaviour`: one of it's children
"""
for child in self.children:
if not direct_descendants:
for node in child.iterate():
yield node
else:
yield child
yield self
[docs] def stop(self, new_status=Status.INVALID):
"""
Args:
new_status (:class:`~py_trees.common.Status`): the behaviour is transitioning to this new status
This calls the user defined :meth:`~py_trees.behaviour.Behaviour.terminate` method and also resets the
generator. It will finally set the new status once the user's :meth:`~py_trees.behaviour.Behaviour.terminate`
function has been called.
.. warning:: Override this method only in exceptional circumstances, prefer overriding :meth:`~py_trees.behaviour.Behaviour.terminate` instead.
"""
self.logger.debug("%s.stop(%s)" % (self.__class__.__name__, "%s->%s" % (self.status, new_status) if self.status != new_status else "%s" % new_status))
self.terminate(new_status)
self.status = new_status
self.iterator = self.tick()
[docs] def verbose_info_string(self):
"""
Override to provide a one line informative string about the behaviour. This
gets used in, e.g. dot graph rendering of the tree.
.. tip::
Use this sparingly. A good use case is for when the behaviour type
and class name isn't sufficient to inform the user about it's
mechanisms for controlling the flow of a tree tick (e.g. parallels
with policies).
"""
return ""