#!/usr/bin/env python
#
# License: BSD
# https://raw.githubusercontent.com/splintered-reality/py_trees/devel/LICENSE
#
##############################################################################
# Documentation
##############################################################################
"""Common definitions, methods and variables used by the py_trees library."""
##############################################################################
# Imports
##############################################################################
import enum
import sys
import typing
##############################################################################
# General
##############################################################################
[docs]class Name(enum.Enum):
"""Naming conventions."""
AUTO_GENERATED = "AUTO_GENERATED"
"""Automagically generate (hopefully) something sensible.."""
[docs]class Status(enum.Enum):
"""An enumerator representing the status of a behaviour."""
SUCCESS = "SUCCESS"
"""Behaviour check has passed, or execution of its action has finished with a successful result."""
FAILURE = "FAILURE"
"""Behaviour check has failed, or execution of its action finished with a failed result."""
RUNNING = "RUNNING"
"""Behaviour is in the middle of executing some action, result still pending."""
INVALID = "INVALID"
"""Behaviour is uninitialised and/or in an inactive state, i.e. not currently being ticked."""
[docs]class Duration(enum.Enum):
"""Naming conventions."""
INFINITE = sys.float_info.max
""":py:data:`~py_trees.common.Duration.INFINITE` oft used for perpetually blocking operations."""
UNTIL_THE_BATTLE_OF_ALFREDO = sys.float_info.max
""":py:data:`~py_trees.common.Duration.UNTIL_THE_BATTLE_OF_ALFREDO` is an alias for
:py:data:`~py_trees.common.Duration.INFINITE`."""
[docs]class Access(enum.Enum):
"""Use to distinguish types of access to, e.g. variables on a blackboard."""
READ = "READ"
"""Read access."""
WRITE = "WRITE"
"""Write access, implicitly also grants read access."""
EXCLUSIVE_WRITE = "EXCLUSIVE_WRITE"
"""Exclusive lock for writing, i.e. no other writer permitted."""
##############################################################################
# Policies
##############################################################################
[docs]class ParallelPolicy(object):
"""Configurable policies for :py:class:`~py_trees.composites.Parallel` behaviours."""
class Base(object):
"""Base class for parallel policies. Should never be used directly."""
def __init__(self, synchronise: bool = False):
"""
Configure the policy to be synchronised or otherwise.
Args:
synchronise: stop ticking of children with status
:py:data:`~py_trees.common.Status.SUCCESS` until the policy criteria is met
"""
self.synchronise = synchronise
[docs] class SuccessOnAll(Base):
"""
Success depends on all children succeeding.
Return :py:data:`~py_trees.common.Status.SUCCESS` only when each and every child returns
:py:data:`~py_trees.common.Status.SUCCESS`. If synchronisation is requested, any children that
tick with :data:`~py_trees.common.Status.SUCCESS` will be skipped on subsequent ticks until
the policy criteria is met, or one of the children returns status :data:`~py_trees.common.Status.FAILURE`.
"""
def __init__(self, synchronise: bool = True):
"""
Policy configuration.
Args:
synchronise (:obj:`bool`): stop ticking of children with status
:py:data:`~py_trees.common.Status.SUCCESS` until the policy criteria is met
"""
super().__init__(synchronise=synchronise)
[docs] class SuccessOnOne(Base):
"""Success depends on just one child (can be any child).
Return :py:data:`~py_trees.common.Status.SUCCESS` so long as at least one child has
:py:data:`~py_trees.common.Status.SUCCESS`
and the remainder are :py:data:`~py_trees.common.Status.RUNNING`
"""
def __init__(self) -> None:
"""No configuration necessary for this policy."""
super().__init__(synchronise=False)
[docs] class SuccessOnSelected(Base):
"""Success depends on an explicitly selected set of children behaviours.
Return :py:data:`~py_trees.common.Status.SUCCESS` so long as each child in a specified list returns
:py:data:`~py_trees.common.Status.SUCCESS`. If synchronisation is requested, any children that
tick with :data:`~py_trees.common.Status.SUCCESS` will be skipped on subsequent ticks until
the policy criteria is met, or one of the children returns status :data:`~py_trees.common.Status.FAILURE`.
"""
def __init__(
self,
# TODO should be behaviour.Behaviour, but cyclic imports -> redesign
children: typing.List[typing.Any],
synchronise: bool = True,
):
"""
Policy configuration.
Args:
children: list of children to succeed on
synchronise: stop ticking of children with status
:py:data:`~py_trees.common.Status.SUCCESS` until the policy criteria is met
"""
super().__init__(synchronise=synchronise)
self.children = children
class OneShotPolicy(enum.Enum):
"""Policy rules for oneshots.
These are used to configure both :py:class:`~py_trees.decorators.OneShot` (decorator)
and py:meth:`~py_trees.idioms.oneshot (idiom) approaches.
"""
ON_COMPLETION = [Status.SUCCESS, Status.FAILURE]
"""Reflect the child/subtree's status as soon as it ticks to completion (success or failure)."""
ON_SUCCESSFUL_COMPLETION = [Status.SUCCESS]
"""Reflect the child/subtree's status only when it succeeds (failures are rerun)."""
[docs]class ClearingPolicy(enum.IntEnum):
"""Policy rules for behaviours to dictate when data should be cleared/reset."""
ON_INITIALISE = 1
"""Clear when entering the :py:meth:`~py_trees.behaviour.Behaviour.initialise` method."""
ON_SUCCESS = 2
"""Clear when returning :py:data:`~py_trees.common.Status.SUCCESS`."""
NEVER = 3
"""Never clear the data"""
##############################################################################
# Blackboards
##############################################################################
# TODO: There might be a case for allowing some widening of the arguments,
# i.e. for left and right hand argument types to the ComparisonExpression
# operator to vary and/or be covariant / contravariant so different classes
# or base/derived variants can be compared.
#
# To do so however, starts to make the machinery awkward (e.g. forcing
# the user to only use the 'value' as a right-hand argument to the operator.
# If this is necessary, a different construct would be better.
#
# NB: Widening all the way to Any results in virally plaguing downstream
# with the ramifications of Any (e.g. any-return).
ComparisonV = typing.TypeVar("ComparisonV")
class ComparisonExpression(object):
"""
Store the parameters for a univariate comparison operation.
A univariate comparison operation compares the relationship between
a variable and a value (e.g. x > 3).
Args:
variable: name of the variable to compare
value: value to compare against
operator: a callable comparison operator
.. tip::
The python `operator module`_ includes many useful comparison operations, e.g. operator.ne
.. code::
import operator
check = ComparisonExpression(
variable="foo",
value= 5,
operator=operator.eq
)
success = check.operator(blackboard[check.variable], check.value)
"""
def __init__(
self,
variable: str,
value: ComparisonV,
operator: typing.Callable[[ComparisonV, ComparisonV], bool],
):
"""Initialise variables.
Args:
variable: the variable name to use
value: the value to use on the RHS of the expression
operator: a logical operator to enable comparisons
"""
self.variable = variable
self.value = value
self.operator = operator
##############################################################################
# BlackBoxes
##############################################################################
[docs]class BlackBoxLevel(enum.IntEnum):
"""
A hint used for visualisation.
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.
`DETAIL`.
"""
DETAIL = 1
"""A blackbox that encapsulates detailed activity."""
COMPONENT = 2
"""A blackbox that encapsulates a subgroup of functionalities as a single group."""
BIG_PICTURE = 3
"""A blackbox that represents a big picture part of the entire tree view."""
NOT_A_BLACKBOX = 4
"""Not a blackbox, do not ever collapse."""
[docs]class VisibilityLevel(enum.IntEnum):
"""
Flag used by visualisation tools to configure visibility..
Closely associated with the :py:class:`~py_trees.common.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."""
DETAIL = BlackBoxLevel.DETAIL
"""Collapse blackboxes marked with :py:data:`~py_trees.common.BlackBoxLevel.DETAIL` or lower."""
COMPONENT = BlackBoxLevel.COMPONENT
"""Collapse blackboxes marked with :py:data:`~py_trees.common.BlackBoxLevel.COMPONENT` or lower."""
BIG_PICTURE = BlackBoxLevel.BIG_PICTURE
"""Collapse any blackbox that isn't marked with :py:data:`~py_trees.common.BlackBoxLevel.BIG_PICTURE`."""
visibility_level_strings = ["all", "detail", "component", "big_picture"]
"""Convenient string representations to use for command line input (amongst other things)."""
def string_to_visibility_level(level: str) -> VisibilityLevel:
"""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.
Args:
level: visibility level as a string
Returns:
:class:`~py_trees.common.VisibilityLevel`: visibility level enum
"""
if level == "detail":
return VisibilityLevel.DETAIL
elif level == "component":
return VisibilityLevel.COMPONENT
elif level == "big_picture":
return VisibilityLevel.BIG_PICTURE
else:
return VisibilityLevel.ALL