Demos

py-trees-demo-action-behaviour

A py_trees demo.

Demonstrates the characteristics of a typical ‘action’ behaviour.

  • Mocks an external process and connects to it in the setup() method

  • Kickstarts new goals with the external process in the initialise() method

  • Monitors the ongoing goal status in the update() method

  • Determines RUNNING/SUCCESS pending feedback from the external process

usage: py-trees-demo-action-behaviour [-h]
_images/action.gif
class py_trees.demos.action.Action(name: str)[source]

Bases: Behaviour

Demonstrates the at-a-distance style action behaviour.

This behaviour connects to a separately running process (initiated in setup()) and proceeeds to work with that subprocess to initiate a task and monitor the progress of that task at each tick until completed. While the task is running the behaviour returns RUNNING.

On completion, the the behaviour returns with success or failure (depending on success or failure of the task itself).

Key point - this behaviour itself should not be doing any work!

Parameters

name (str) –

__init__(name: str)[source]

Configure the name of the behaviour.

Parameters

name (str) –

initialise() None[source]

Reset a counter variable.

Return type

None

setup(**kwargs: int) None[source]

Kickstart the separate process this behaviour will work with.

Ordinarily this process will be already running. In this case, setup is usually just responsible for verifying it exists.

Parameters

kwargs (int) –

Return type

None

terminate(new_status: Status) None[source]

Nothing to clean up in this example.

Parameters

new_status (Status) –

Return type

None

update() Status[source]

Increment the counter, monitor and decide on a new status.

Return type

Status

py_trees.demos.action.command_line_argument_parser() ArgumentParser[source]

Process command line arguments.

Returns

the argument parser

Return type

ArgumentParser

py_trees.demos.action.description() str[source]

Print description and usage information about the program.

Returns

the program description string

Return type

str

py_trees.demos.action.epilog() Optional[str][source]

Print a noodly epilog for –help.

Returns

the noodly message

Return type

Optional[str]

py_trees.demos.action.main() None[source]

Entry point for the demo script.

Return type

None

py_trees.demos.action.planning(pipe_connection: Connection) None[source]

Emulate a (potentially) long running external process.

Parameters

pipe_connection (Connection) – connection to the planning process

Return type

None

py_trees/demos/action.py
  1#!/usr/bin/env python
  2#
  3# License: BSD
  4#   https://raw.githubusercontent.com/splintered-reality/py_trees/devel/LICENSE
  5#
  6##############################################################################
  7# Documentation
  8##############################################################################
  9
 10"""
 11A py_trees demo.
 12
 13.. argparse::
 14   :module: py_trees.demos.action
 15   :func: command_line_argument_parser
 16   :prog: py-trees-demo-action-behaviour
 17
 18.. image:: images/action.gif
 19"""
 20
 21##############################################################################
 22# Imports
 23##############################################################################
 24
 25import argparse
 26import atexit
 27import multiprocessing
 28import multiprocessing.connection
 29import time
 30import typing
 31
 32import py_trees.common
 33import py_trees.console as console
 34
 35##############################################################################
 36# Classes
 37##############################################################################
 38
 39
 40def description() -> str:
 41    """
 42    Print description and usage information about the program.
 43
 44    Returns:
 45       the program description string
 46    """
 47    content = "Demonstrates the characteristics of a typical 'action' behaviour.\n"
 48    content += "\n"
 49    content += "* Mocks an external process and connects to it in the setup() method\n"
 50    content += (
 51        "* Kickstarts new goals with the external process in the initialise() method\n"
 52    )
 53    content += "* Monitors the ongoing goal status in the update() method\n"
 54    content += (
 55        "* Determines RUNNING/SUCCESS pending feedback from the external process\n"
 56    )
 57
 58    if py_trees.console.has_colours:
 59        banner_line = console.green + "*" * 79 + "\n" + console.reset
 60        s = banner_line
 61        s += console.bold_white + "Action Behaviour".center(79) + "\n" + console.reset
 62        s += banner_line
 63        s += "\n"
 64        s += content
 65        s += "\n"
 66        s += banner_line
 67    else:
 68        s = content
 69    return s
 70
 71
 72def epilog() -> typing.Optional[str]:
 73    """
 74    Print a noodly epilog for --help.
 75
 76    Returns:
 77       the noodly message
 78    """
 79    if py_trees.console.has_colours:
 80        return (
 81            console.cyan
 82            + "And his noodly appendage reached forth to tickle the blessed...\n"
 83            + console.reset
 84        )
 85    else:
 86        return None
 87
 88
 89def command_line_argument_parser() -> argparse.ArgumentParser:
 90    """
 91    Process command line arguments.
 92
 93    Returns:
 94        the argument parser
 95    """
 96    return argparse.ArgumentParser(
 97        description=description(),
 98        epilog=epilog(),
 99        formatter_class=argparse.RawDescriptionHelpFormatter,
100    )
101
102
103def planning(pipe_connection: multiprocessing.connection.Connection) -> None:
104    """Emulate a (potentially) long running external process.
105
106    Args:
107        pipe_connection: connection to the planning process
108    """
109    idle = True
110    percentage_complete = 0
111    try:
112        while True:
113            if pipe_connection.poll():
114                pipe_connection.recv()
115                percentage_complete = 0
116                idle = False
117            if not idle:
118                percentage_complete += 10
119                pipe_connection.send([percentage_complete])
120                if percentage_complete == 100:
121                    idle = True
122            time.sleep(0.5)
123    except KeyboardInterrupt:
124        pass
125
126
127class Action(py_trees.behaviour.Behaviour):
128    """Demonstrates the at-a-distance style action behaviour.
129
130    This behaviour connects to a separately running process
131    (initiated in setup()) and proceeeds to work with that subprocess to
132    initiate a task and monitor the progress of that task at each tick
133    until completed. While the task is running the behaviour returns
134    :data:`~py_trees.common.Status.RUNNING`.
135
136    On completion, the the behaviour returns with success or failure
137    (depending on success or failure of the task itself).
138
139    Key point - this behaviour itself should not be doing any work!
140    """
141
142    def __init__(self, name: str):
143        """Configure the name of the behaviour."""
144        super(Action, self).__init__(name)
145        self.logger.debug("%s.__init__()" % (self.__class__.__name__))
146
147    def setup(self, **kwargs: int) -> None:
148        """Kickstart the separate process this behaviour will work with.
149
150        Ordinarily this process will be already running. In this case,
151        setup is usually just responsible for verifying it exists.
152        """
153        self.logger.debug(
154            "%s.setup()->connections to an external process" % (self.__class__.__name__)
155        )
156        self.parent_connection, self.child_connection = multiprocessing.Pipe()
157        self.planning = multiprocessing.Process(
158            target=planning, args=(self.child_connection,)
159        )
160        atexit.register(self.planning.terminate)
161        self.planning.start()
162
163    def initialise(self) -> None:
164        """Reset a counter variable."""
165        self.logger.debug(
166            "%s.initialise()->sending new goal" % (self.__class__.__name__)
167        )
168        self.parent_connection.send(["new goal"])
169        self.percentage_completion = 0
170
171    def update(self) -> py_trees.common.Status:
172        """Increment the counter, monitor and decide on a new status."""
173        new_status = py_trees.common.Status.RUNNING
174        if self.parent_connection.poll():
175            self.percentage_completion = self.parent_connection.recv().pop()
176            if self.percentage_completion == 100:
177                new_status = py_trees.common.Status.SUCCESS
178        if new_status == py_trees.common.Status.SUCCESS:
179            self.feedback_message = "Processing finished"
180            self.logger.debug(
181                "%s.update()[%s->%s][%s]"
182                % (
183                    self.__class__.__name__,
184                    self.status,
185                    new_status,
186                    self.feedback_message,
187                )
188            )
189        else:
190            self.feedback_message = "{0}%".format(self.percentage_completion)
191            self.logger.debug(
192                "%s.update()[%s][%s]"
193                % (self.__class__.__name__, self.status, self.feedback_message)
194            )
195        return new_status
196
197    def terminate(self, new_status: py_trees.common.Status) -> None:
198        """Nothing to clean up in this example."""
199        self.logger.debug(
200            "%s.terminate()[%s->%s]"
201            % (self.__class__.__name__, self.status, new_status)
202        )
203
204
205##############################################################################
206# Main
207##############################################################################
208
209
210def main() -> None:
211    """Entry point for the demo script."""
212    command_line_argument_parser().parse_args()
213
214    print(description())
215
216    py_trees.logging.level = py_trees.logging.Level.DEBUG
217
218    action = Action(name="Action")
219    action.setup()
220    try:
221        for _unused_i in range(0, 12):
222            action.tick_once()
223            time.sleep(0.5)
224        print("\n")
225    except KeyboardInterrupt:
226        pass

py-trees-demo-behaviour-lifecycle

A py_trees demo.

. argparse::
module

py_trees.demos.lifecycle

func

command_line_argument_parser

prog

py-trees-demo-behaviour-lifecycle

_images/lifecycle.gif
class py_trees.demos.lifecycle.Counter(name: str = 'Counter')[source]

Bases: Behaviour

Simple counting behaviour.

  • Increments a counter from zero at each tick

  • Finishes with success if the counter reaches three

  • Resets the counter in the initialise() method.

Parameters

name (str) –

__init__(name: str = 'Counter')[source]

Configure the name of the behaviour.

Parameters

name (str) –

initialise() None[source]

Reset a counter variable.

Return type

None

setup(**kwargs: int) None[source]

No delayed initialisation required for this example.

Parameters

kwargs (int) –

Return type

None

terminate(new_status: Status) None[source]

Nothing to clean up in this example.

Parameters

new_status (Status) –

Return type

None

update() Status[source]

Increment the counter and decide on a new status.

Return type

Status

py_trees.demos.lifecycle.command_line_argument_parser() ArgumentParser[source]

Process command line arguments.

Returns

the argument parser

Return type

ArgumentParser

py_trees.demos.lifecycle.description() str[source]

Print description and usage information about the program.

Returns

the program description string

Return type

str

py_trees.demos.lifecycle.epilog() Optional[str][source]

Print a noodly epilog for –help.

Returns

the noodly message

Return type

Optional[str]

py_trees.demos.lifecycle.main() None[source]

Entry point for the demo script.

Return type

None

py_trees/demos/lifecycle.py
  1#!/usr/bin/env python
  2#
  3# License: BSD
  4#   https://raw.githubusercontent.com/splintered-reality/py_trees/devel/LICENSE
  5#
  6##############################################################################
  7# Documentation
  8##############################################################################
  9
 10"""
 11A py_trees demo.
 12
 13. argparse::
 14   :module: py_trees.demos.lifecycle
 15   :func: command_line_argument_parser
 16   :prog: py-trees-demo-behaviour-lifecycle
 17
 18.. image:: images/lifecycle.gif
 19"""
 20
 21##############################################################################
 22# Imports
 23##############################################################################
 24
 25import argparse
 26import time
 27import typing
 28
 29import py_trees
 30import py_trees.console as console
 31
 32##############################################################################
 33# Classes
 34##############################################################################
 35
 36
 37def description() -> str:
 38    """
 39    Print description and usage information about the program.
 40
 41    Returns:
 42       the program description string
 43    """
 44    content = "Demonstrates a typical day in the life of a behaviour.\n\n"
 45    content += (
 46        "This behaviour will count from 1 to 3 and then reset and repeat. As it does\n"
 47    )
 48    content += "so, it logs and displays the methods as they are called - construction, setup,\n"
 49    content += "initialisation, ticking and termination.\n"
 50    if py_trees.console.has_colours:
 51        banner_line = console.green + "*" * 79 + "\n" + console.reset
 52        s = banner_line
 53        s += (
 54            console.bold_white + "Behaviour Lifecycle".center(79) + "\n" + console.reset
 55        )
 56        s += banner_line
 57        s += "\n"
 58        s += content
 59        s += "\n"
 60        s += banner_line
 61    else:
 62        s = content
 63    return s
 64
 65
 66def epilog() -> typing.Optional[str]:
 67    """
 68    Print a noodly epilog for --help.
 69
 70    Returns:
 71       the noodly message
 72    """
 73    if py_trees.console.has_colours:
 74        return (
 75            console.cyan
 76            + "And his noodly appendage reached forth to tickle the blessed...\n"
 77            + console.reset
 78        )
 79    else:
 80        return None
 81
 82
 83def command_line_argument_parser() -> argparse.ArgumentParser:
 84    """
 85    Process command line arguments.
 86
 87    Returns:
 88        the argument parser
 89    """
 90    return argparse.ArgumentParser(
 91        description=description(),
 92        epilog=epilog(),
 93        formatter_class=argparse.RawDescriptionHelpFormatter,
 94    )
 95
 96
 97class Counter(py_trees.behaviour.Behaviour):
 98    """Simple counting behaviour.
 99
100    * Increments a counter from zero at each tick
101    * Finishes with success if the counter reaches three
102    * Resets the counter in the initialise() method.
103    """
104
105    def __init__(self, name: str = "Counter"):
106        """Configure the name of the behaviour."""
107        super(Counter, self).__init__(name)
108        self.logger.debug("%s.__init__()" % (self.__class__.__name__))
109
110    def setup(self, **kwargs: int) -> None:
111        """No delayed initialisation required for this example."""
112        self.logger.debug("%s.setup()" % (self.__class__.__name__))
113
114    def initialise(self) -> None:
115        """Reset a counter variable."""
116        self.logger.debug("%s.initialise()" % (self.__class__.__name__))
117        self.counter = 0
118
119    def update(self) -> py_trees.common.Status:
120        """Increment the counter and decide on a new status."""
121        self.counter += 1
122        new_status = (
123            py_trees.common.Status.SUCCESS
124            if self.counter == 3
125            else py_trees.common.Status.RUNNING
126        )
127        if new_status == py_trees.common.Status.SUCCESS:
128            self.feedback_message = (
129                "counting...{0} - phew, thats enough for today".format(self.counter)
130            )
131        else:
132            self.feedback_message = "still counting"
133        self.logger.debug(
134            "%s.update()[%s->%s][%s]"
135            % (self.__class__.__name__, self.status, new_status, self.feedback_message)
136        )
137        return new_status
138
139    def terminate(self, new_status: py_trees.common.Status) -> None:
140        """Nothing to clean up in this example."""
141        self.logger.debug(
142            "%s.terminate()[%s->%s]"
143            % (self.__class__.__name__, self.status, new_status)
144        )
145
146
147##############################################################################
148# Main
149##############################################################################
150
151
152def main() -> None:
153    """Entry point for the demo script."""
154    command_line_argument_parser().parse_args()
155
156    print(description())
157
158    py_trees.logging.level = py_trees.logging.Level.DEBUG
159
160    counter = Counter()
161    counter.setup()
162    try:
163        for _unused_i in range(0, 7):
164            counter.tick_once()
165            time.sleep(0.5)
166        print("\n")
167    except KeyboardInterrupt:
168        print("")
169        pass

py-trees-demo-blackboard

A py_trees demo.

Demonstrates usage of the blackboard and related behaviours.

A sequence is populated with a few behaviours that exercise reading and writing on the Blackboard in interesting ways.

usage: py-trees-demo-blackboard [-h] [-r | --render-with-blackboard-variables]

Named Arguments

-r, --render

render dot tree to file

Default: False

--render-with-blackboard-variables

render dot tree to file with blackboard variables

Default: False

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];
}

Dot Graph

_images/blackboard_demo.png

Console Screenshot

class py_trees.demos.blackboard.BlackboardWriter(name: str)[source]

Bases: Behaviour

Write some more interesting / complex types to the blacbkoard.

Parameters

name (str) –

__init__(name: str)[source]

Set up the blackboard.

Parameters

name (str) – behaviour name

update() Status[source]

Write a dictionary to the blackboard.

This beaviour always returns SUCCESS.

Return type

Status

class py_trees.demos.blackboard.Nested[source]

Bases: object

A more complex object to interact with on the blackboard.

__init__() None[source]

Initialise variables to some arbitrary defaults.

Return type

None

__str__() str[source]

Return str(self).

Return type

str

__weakref__

list of weak references to the object (if defined)

class py_trees.demos.blackboard.ParamsAndState(name: str)[source]

Bases: Behaviour

Parameter and state storage on the blackboard.

This behaviour demonstrates the usage of namespaces and multiple clients to perform getting and setting of parameters and state in a concise and convenient manner.

Parameters

name (str) –

__init__(name: str)[source]

Set up separate blackboard clients for parameters and state.

Parameters

name (str) – behaviour name

initialise() None[source]

Initialise speed from the stored parameter variable on the blackboard.

Return type

None

update() Status[source]

Check speed and either increment, or complete if it has reached a threshold.

Returns

RUNNING if incrementing, SUCCESS otherwise.

Return type

Status

py_trees.demos.blackboard.command_line_argument_parser() ArgumentParser[source]

Process command line arguments.

Returns

the argument parser

Return type

ArgumentParser

py_trees.demos.blackboard.create_root() Behaviour[source]

Create the root behaviour and it’s subtree.

Returns

the root behaviour

Return type

Behaviour

py_trees.demos.blackboard.description() str[source]

Print description and usage information about the program.

Returns

the program description string

Return type

str

py_trees.demos.blackboard.epilog() Optional[str][source]

Print a noodly epilog for –help.

Returns

the noodly message

Return type

Optional[str]

py_trees.demos.blackboard.main() None[source]

Entry point for the demo script.

Return type

None

py_trees/demos/blackboard.py
  1#!/usr/bin/env python
  2#
  3# License: BSD
  4#   https://raw.githubusercontent.com/splintered-reality/py_trees/devel/LICENSE
  5#
  6##############################################################################
  7# Documentation
  8##############################################################################
  9
 10"""
 11A py_trees demo.
 12
 13.. argparse::
 14   :module: py_trees.demos.blackboard
 15   :func: command_line_argument_parser
 16   :prog: py-trees-demo-blackboard
 17
 18.. graphviz:: dot/demo-blackboard.dot
 19   :align: center
 20   :caption: Dot Graph
 21
 22.. figure:: images/blackboard_demo.png
 23   :align: center
 24
 25   Console Screenshot
 26"""
 27
 28##############################################################################
 29# Imports
 30##############################################################################
 31
 32import argparse
 33import operator
 34import sys
 35import typing
 36
 37import py_trees
 38import py_trees.console as console
 39
 40##############################################################################
 41# Classes
 42##############################################################################
 43
 44
 45def description() -> str:
 46    """
 47    Print description and usage information about the program.
 48
 49    Returns:
 50       the program description string
 51    """
 52    content = "Demonstrates usage of the blackboard and related behaviours.\n"
 53    content += "\n"
 54    content += "A sequence is populated with a few behaviours that exercise\n"
 55    content += "reading and writing on the Blackboard in interesting ways.\n"
 56
 57    if py_trees.console.has_colours:
 58        banner_line = console.green + "*" * 79 + "\n" + console.reset
 59        s = banner_line
 60        s += console.bold_white + "Blackboard".center(79) + "\n" + console.reset
 61        s += banner_line
 62        s += "\n"
 63        s += content
 64        s += "\n"
 65        s += banner_line
 66    else:
 67        s = content
 68    return s
 69
 70
 71def epilog() -> typing.Optional[str]:
 72    """
 73    Print a noodly epilog for --help.
 74
 75    Returns:
 76       the noodly message
 77    """
 78    if py_trees.console.has_colours:
 79        return (
 80            console.cyan
 81            + "And his noodly appendage reached forth to tickle the blessed...\n"
 82            + console.reset
 83        )
 84    else:
 85        return None
 86
 87
 88def command_line_argument_parser() -> argparse.ArgumentParser:
 89    """
 90    Process command line arguments.
 91
 92    Returns:
 93        the argument parser
 94    """
 95    parser = argparse.ArgumentParser(
 96        description=description(),
 97        epilog=epilog(),
 98        formatter_class=argparse.RawDescriptionHelpFormatter,
 99    )
100    render_group = parser.add_mutually_exclusive_group()
101    render_group.add_argument(
102        "-r", "--render", action="store_true", help="render dot tree to file"
103    )
104    render_group.add_argument(
105        "--render-with-blackboard-variables",
106        action="store_true",
107        help="render dot tree to file with blackboard variables",
108    )
109    return parser
110
111
112class Nested(object):
113    """A more complex object to interact with on the blackboard."""
114
115    def __init__(self) -> None:
116        """Initialise variables to some arbitrary defaults."""
117        self.foo = "bar"
118
119    def __str__(self) -> str:
120        return str({"foo": self.foo})
121
122
123class BlackboardWriter(py_trees.behaviour.Behaviour):
124    """Write some more interesting / complex types to the blacbkoard."""
125
126    def __init__(self, name: str):
127        """Set up the blackboard.
128
129        Args:
130            name: behaviour name
131        """
132        super().__init__(name=name)
133        self.blackboard = self.attach_blackboard_client()
134        self.blackboard.register_key(key="dude", access=py_trees.common.Access.READ)
135        self.blackboard.register_key(
136            key="spaghetti", access=py_trees.common.Access.WRITE
137        )
138
139        self.logger.debug("%s.__init__()" % (self.__class__.__name__))
140
141    def update(self) -> py_trees.common.Status:
142        """Write a dictionary to the blackboard.
143
144        This beaviour always returns :data:`~py_trees.common.Status.SUCCESS`.
145        """
146        self.logger.debug("%s.update()" % (self.__class__.__name__))
147        try:
148            _ = self.blackboard.dude
149        except KeyError:
150            pass
151        try:
152            _ = self.blackboard.dudette
153        except AttributeError:
154            pass
155        try:
156            self.blackboard.dudette = "Jane"
157        except AttributeError:
158            pass
159        self.blackboard.spaghetti = {"type": "Carbonara", "quantity": 1}
160        self.blackboard.spaghetti = {"type": "Gnocchi", "quantity": 2}
161        try:
162            self.blackboard.set(
163                "spaghetti", {"type": "Bolognese", "quantity": 3}, overwrite=False
164            )
165        except AttributeError:
166            pass
167        return py_trees.common.Status.SUCCESS
168
169
170class ParamsAndState(py_trees.behaviour.Behaviour):
171    """Parameter and state storage on the blackboard.
172
173    This behaviour demonstrates the usage of namespaces and
174    multiple clients to perform getting and setting of
175    parameters and state in a concise and convenient manner.
176    """
177
178    def __init__(self, name: str):
179        """Set up separate blackboard clients for parameters and state.
180
181        Args:
182           name: behaviour name
183        """
184        super().__init__(name=name)
185        # namespaces can include the separator or may leave it out
186        # they can also be nested, e.g. /agent/state, /agent/parameters
187        self.parameters = self.attach_blackboard_client("Params", "parameters")
188        self.state = self.attach_blackboard_client("State", "state")
189        self.parameters.register_key(
190            key="default_speed", access=py_trees.common.Access.READ
191        )
192        self.state.register_key(
193            key="current_speed", access=py_trees.common.Access.WRITE
194        )
195
196    def initialise(self) -> None:
197        """Initialise speed from the stored parameter variable on the blackboard."""
198        try:
199            self.state.current_speed = self.parameters.default_speed
200        except KeyError as e:
201            raise RuntimeError(
202                "parameter 'default_speed' not found [{}]".format(str(e))
203            )
204
205    def update(self) -> py_trees.common.Status:
206        """
207        Check speed and either increment, or complete if it has reached a threshold.
208
209        Returns:
210            :data:`~py_trees.common.Status.RUNNING` if incrementing, :data:`~py_trees.common.Status.SUCCESS` otherwise.
211        """
212        if self.state.current_speed > 40.0:
213            return py_trees.common.Status.SUCCESS
214        else:
215            self.state.current_speed += 1.0
216            return py_trees.common.Status.RUNNING
217
218
219def create_root() -> py_trees.behaviour.Behaviour:
220    """
221    Create the root behaviour and it's subtree.
222
223    Returns:
224        the root behaviour
225    """
226    root = py_trees.composites.Sequence(name="Blackboard Demo", memory=True)
227    set_blackboard_variable = py_trees.behaviours.SetBlackboardVariable(
228        name="Set Nested",
229        variable_name="nested",
230        variable_value=Nested(),
231        overwrite=True,
232    )
233    write_blackboard_variable = BlackboardWriter(name="Writer")
234    check_blackboard_variable = py_trees.behaviours.CheckBlackboardVariableValue(
235        name="Check Nested Foo",
236        check=py_trees.common.ComparisonExpression(
237            variable="nested.foo", value="bar", operator=operator.eq
238        ),
239    )
240    params_and_state = ParamsAndState(name="ParamsAndState")
241    root.add_children(
242        [
243            set_blackboard_variable,
244            write_blackboard_variable,
245            check_blackboard_variable,
246            params_and_state,
247        ]
248    )
249    return root
250
251
252##############################################################################
253# Main
254##############################################################################
255
256
257def main() -> None:
258    """Entry point for the demo script."""
259    args = command_line_argument_parser().parse_args()
260    print(description())
261    py_trees.logging.level = py_trees.logging.Level.DEBUG
262    py_trees.blackboard.Blackboard.enable_activity_stream(maximum_size=100)
263    blackboard = py_trees.blackboard.Client(name="Configuration")
264    blackboard.register_key(key="dude", access=py_trees.common.Access.WRITE)
265    blackboard.register_key(
266        key="/parameters/default_speed", access=py_trees.common.Access.EXCLUSIVE_WRITE
267    )
268    blackboard.dude = "Bob"
269    blackboard.parameters.default_speed = 30.0
270
271    root = create_root()
272
273    ####################
274    # Rendering
275    ####################
276    if args.render:
277        py_trees.display.render_dot_tree(root, with_blackboard_variables=False)
278        sys.exit()
279    if args.render_with_blackboard_variables:
280        py_trees.display.render_dot_tree(root, with_blackboard_variables=True)
281        sys.exit()
282
283    ####################
284    # Execute
285    ####################
286    root.setup_with_descendants()
287    unset_blackboard = py_trees.blackboard.Client(name="Unsetter")
288    unset_blackboard.register_key(key="foo", access=py_trees.common.Access.WRITE)
289    print("\n--------- Tick 0 ---------\n")
290    root.tick_once()
291    print("\n")
292    print(py_trees.display.unicode_tree(root, show_status=True))
293    print("--------------------------\n")
294    print(py_trees.display.unicode_blackboard())
295    print("--------------------------\n")
296    print(py_trees.display.unicode_blackboard(display_only_key_metadata=True))
297    print("--------------------------\n")
298    unset_blackboard.unset("foo")
299    print(py_trees.display.unicode_blackboard_activity_stream())

py-trees-demo-blackboard-namespaces

A py_trees demo.

Demonstrates usage of blackboard namespaces.

usage: py-trees-demo-blackboard-namespaces [-h]
_images/blackboard_namespaces.png

Console Screenshot

py_trees.demos.blackboard_namespaces.command_line_argument_parser() ArgumentParser[source]

Process command line arguments.

Returns

the argument parser

Return type

ArgumentParser

py_trees.demos.blackboard_namespaces.description() str[source]

Print description and usage information about the program.

Returns

the program description string

Return type

str

py_trees.demos.blackboard_namespaces.epilog() Optional[str][source]

Print a noodly epilog for –help.

Returns

the noodly message

Return type

Optional[str]

py_trees.demos.blackboard_namespaces.main() None[source]

Entry point for the demo script.

Return type

None

py_trees/demos/blackboard_namespaces.py
  1#!/usr/bin/env python
  2#
  3# License: BSD
  4#   https://raw.githubusercontent.com/splintered-reality/py_trees/devel/LICENSE
  5#
  6##############################################################################
  7# Documentation
  8##############################################################################
  9
 10"""
 11A py_trees demo.
 12
 13.. argparse::
 14   :module: py_trees.demos.blackboard_namespaces
 15   :func: command_line_argument_parser
 16   :prog: py-trees-demo-blackboard-namespaces
 17
 18.. figure:: images/blackboard_namespaces.png
 19   :align: center
 20
 21   Console Screenshot
 22"""
 23
 24##############################################################################
 25# Imports
 26##############################################################################
 27
 28import argparse
 29import typing
 30
 31import py_trees
 32import py_trees.console as console
 33
 34##############################################################################
 35# Classes
 36##############################################################################
 37
 38
 39def description() -> str:
 40    """
 41    Print description and usage information about the program.
 42
 43    Returns:
 44       the program description string
 45    """
 46    content = "Demonstrates usage of blackboard namespaces.\n"
 47    content += "\n"
 48
 49    if py_trees.console.has_colours:
 50        banner_line = console.green + "*" * 79 + "\n" + console.reset
 51        s = banner_line
 52        s += console.bold_white + "Blackboard".center(79) + "\n" + console.reset
 53        s += banner_line
 54        s += "\n"
 55        s += content
 56        s += "\n"
 57        s += banner_line
 58    else:
 59        s = content
 60    return s
 61
 62
 63def epilog() -> typing.Optional[str]:
 64    """
 65    Print a noodly epilog for --help.
 66
 67    Returns:
 68       the noodly message
 69    """
 70    if py_trees.console.has_colours:
 71        return (
 72            console.cyan
 73            + "And his noodly appendage reached forth to tickle the blessed...\n"
 74            + console.reset
 75        )
 76    else:
 77        return None
 78
 79
 80def command_line_argument_parser() -> argparse.ArgumentParser:
 81    """
 82    Process command line arguments.
 83
 84    Returns:
 85        the argument parser
 86    """
 87    parser = argparse.ArgumentParser(
 88        description=description(),
 89        epilog=epilog(),
 90        formatter_class=argparse.RawDescriptionHelpFormatter,
 91    )
 92    return parser
 93
 94
 95##############################################################################
 96# Main
 97##############################################################################
 98
 99
100def main() -> None:
101    """Entry point for the demo script."""
102    _ = (
103        command_line_argument_parser().parse_args()
104    )  # configuration only, no args to process
105    print(description())
106    print(
107        "-------------------------------------------------------------------------------"
108    )
109    print("$ py_trees.blackboard.Client(name='Blackboard')")
110    print("$ foo.register_key(key='dude', access=py_trees.common.Access.WRITE)")
111    print("$ foo.register_key(key='/dudette', access=py_trees.common.Access.WRITE)")
112    print("$ foo.register_key(key='/foo/bar/wow', access=py_trees.common.Access.WRITE)")
113    print(
114        "-------------------------------------------------------------------------------"
115    )
116    blackboard = py_trees.blackboard.Client(name="Blackboard")
117    blackboard.register_key(key="dude", access=py_trees.common.Access.WRITE)
118    blackboard.register_key(key="/dudette", access=py_trees.common.Access.WRITE)
119    blackboard.register_key(key="/foo/bar/wow", access=py_trees.common.Access.WRITE)
120    print(blackboard)
121    print(
122        "-------------------------------------------------------------------------------"
123    )
124    print("$ blackboard.dude = 'Bob'")
125    print("$ blackboard.dudette = 'Jade'")
126    print(
127        "-------------------------------------------------------------------------------"
128    )
129    blackboard.dude = "Bob"
130    blackboard.dudette = "Jade"
131    print(py_trees.display.unicode_blackboard())
132    print(
133        "-------------------------------------------------------------------------------"
134    )
135    print("$ blackboard.foo.bar.wow = 'foobar'")
136    print(
137        "-------------------------------------------------------------------------------"
138    )
139    blackboard.foo.bar.wow = "foobar"
140    print(py_trees.display.unicode_blackboard())
141    print(
142        "-------------------------------------------------------------------------------"
143    )
144    print("$ py_trees.blackboard.Client(name='Foo', namespace='foo')")
145    print("$ foo.register_key(key='awesome', access=py_trees.common.Access.WRITE)")
146    print("$ foo.register_key(key='/brilliant', access=py_trees.common.Access.WRITE)")
147    print("$ foo.register_key(key='/foo/clever', access=py_trees.common.Access.WRITE)")
148    print(
149        "-------------------------------------------------------------------------------"
150    )
151    foo = py_trees.blackboard.Client(name="Foo", namespace="foo")
152    foo.register_key(key="awesome", access=py_trees.common.Access.WRITE)
153    # TODO: should /brilliant be namespaced or go directly to root?
154    foo.register_key(key="/brilliant", access=py_trees.common.Access.WRITE)
155    # absolute names are ok, so long as they include the namespace
156    foo.register_key(key="/foo/clever", access=py_trees.common.Access.WRITE)
157    print(foo)
158    print(
159        "-------------------------------------------------------------------------------"
160    )
161    print("$ foo.awesome = True")
162    print("$ foo.set('/brilliant', False)")
163    print("$ foo.clever = True")
164    print(
165        "-------------------------------------------------------------------------------"
166    )
167    foo.awesome = True
168    # Only accessable via set since it's not in the namespace
169    foo.set("/brilliant", False)
170    # This will fail since it looks for the namespaced /foo/brilliant key
171    # foo.brilliant = False
172    foo.clever = True
173    print(py_trees.display.unicode_blackboard())

py-trees-demo-blackboard-remappings

A py_trees demo.

Demonstrates usage of blackbord remappings.

Demonstration is via an exemplar behaviour making use of remappings..

usage: py-trees-demo-blackboard-remappings [-h]
_images/blackboard_remappings.png

Console Screenshot

class py_trees.demos.blackboard_remappings.Remap(name: str, remap_to: Dict[str, str])[source]

Bases: Behaviour

Custom writer that submits a more complicated variable to the blackboard.

Parameters
__init__(name: str, remap_to: Dict[str, str])[source]

Set up the blackboard and remap variables.

Parameters
  • name (str) – behaviour name

  • remap_to (Dict[str, str]) – remappings (from variable name to variable name)

update() Status[source]

Write a dictionary to the blackboard.

This beaviour always returns SUCCESS.

Return type

Status

py_trees.demos.blackboard_remappings.command_line_argument_parser() ArgumentParser[source]

Process command line arguments.

Returns

the argument parser

Return type

ArgumentParser

py_trees.demos.blackboard_remappings.description() str[source]

Print description and usage information about the program.

Returns

the program description string

Return type

str

py_trees.demos.blackboard_remappings.epilog() Optional[str][source]

Print a noodly epilog for –help.

Returns

the noodly message

Return type

Optional[str]

py_trees.demos.blackboard_remappings.main() None[source]

Entry point for the demo script.

Return type

None

py_trees/demos/blackboard_remappings.py
  1#!/usr/bin/env python
  2#
  3# License: BSD
  4#   https://raw.githubusercontent.com/splintered-reality/py_trees/devel/LICENSE
  5#
  6##############################################################################
  7# Documentation
  8##############################################################################
  9
 10"""
 11A py_trees demo.
 12
 13.. argparse::
 14   :module: py_trees.demos.blackboard_remappings
 15   :func: command_line_argument_parser
 16   :prog: py-trees-demo-blackboard-remappings
 17
 18.. figure:: images/blackboard_remappings.png
 19   :align: center
 20
 21   Console Screenshot
 22"""
 23
 24##############################################################################
 25# Imports
 26##############################################################################
 27
 28import argparse
 29import typing
 30
 31import py_trees
 32import py_trees.console as console
 33
 34##############################################################################
 35# Classes
 36##############################################################################
 37
 38
 39def description() -> str:
 40    """
 41    Print description and usage information about the program.
 42
 43    Returns:
 44       the program description string
 45    """
 46    content = "Demonstrates usage of blackbord remappings.\n"
 47    content += "\n"
 48    content += "Demonstration is via an exemplar behaviour making use of remappings..\n"
 49
 50    if py_trees.console.has_colours:
 51        banner_line = console.green + "*" * 79 + "\n" + console.reset
 52        s = banner_line
 53        s += console.bold_white + "Blackboard".center(79) + "\n" + console.reset
 54        s += banner_line
 55        s += "\n"
 56        s += content
 57        s += "\n"
 58        s += banner_line
 59    else:
 60        s = content
 61    return s
 62
 63
 64def epilog() -> typing.Optional[str]:
 65    """
 66    Print a noodly epilog for --help.
 67
 68    Returns:
 69       the noodly message
 70    """
 71    if py_trees.console.has_colours:
 72        return (
 73            console.cyan
 74            + "And his noodly appendage reached forth to tickle the blessed...\n"
 75            + console.reset
 76        )
 77    else:
 78        return None
 79
 80
 81def command_line_argument_parser() -> argparse.ArgumentParser:
 82    """
 83    Process command line arguments.
 84
 85    Returns:
 86        the argument parser
 87    """
 88    parser = argparse.ArgumentParser(
 89        description=description(),
 90        epilog=epilog(),
 91        formatter_class=argparse.RawDescriptionHelpFormatter,
 92    )
 93    return parser
 94
 95
 96class Remap(py_trees.behaviour.Behaviour):
 97    """Custom writer that submits a more complicated variable to the blackboard."""
 98
 99    def __init__(self, name: str, remap_to: typing.Dict[str, str]):
100        """
101        Set up the blackboard and remap variables.
102
103        Args:
104            name: behaviour name
105            remap_to: remappings (from variable name to variable name)
106        """
107        super().__init__(name=name)
108        self.logger.debug("%s.__init__()" % (self.__class__.__name__))
109        self.blackboard = self.attach_blackboard_client()
110        self.blackboard.register_key(
111            key="/foo/bar/wow",
112            access=py_trees.common.Access.WRITE,
113            remap_to=remap_to["/foo/bar/wow"],
114        )
115
116    def update(self) -> py_trees.common.Status:
117        """Write a dictionary to the blackboard.
118
119        This beaviour always returns :data:`~py_trees.common.Status.SUCCESS`.
120        """
121        self.logger.debug("%s.update()" % (self.__class__.__name__))
122        self.blackboard.foo.bar.wow = "colander"
123
124        return py_trees.common.Status.SUCCESS
125
126
127##############################################################################
128# Main
129##############################################################################
130
131
132def main() -> None:
133    """Entry point for the demo script."""
134    _ = (
135        command_line_argument_parser().parse_args()
136    )  # configuration only, no arg processing
137    print(description())
138    py_trees.logging.level = py_trees.logging.Level.DEBUG
139    py_trees.blackboard.Blackboard.enable_activity_stream(maximum_size=100)
140    root = Remap(name="Remap", remap_to={"/foo/bar/wow": "/parameters/wow"})
141
142    ####################
143    # Execute
144    ####################
145    root.tick_once()
146    print(root.blackboard)
147    print(py_trees.display.unicode_blackboard())
148    print(py_trees.display.unicode_blackboard_activity_stream())

py-trees-demo-context-switching

A py_trees demo.

Demonstrates context switching with parallels and sequences.

A context switching behaviour is run in parallel with a work sequence. Switching the context occurs in the initialise() and terminate() methods of the context switching behaviour. Note that whether the sequence results in failure or success, the context switch behaviour will always call the terminate() method to restore the context. It will also call terminate() to restore the context in the event of a higher priority parent cancelling this parallel subtree.

usage: py-trees-demo-context-switching [-h] [-r]

Named Arguments

-r, --render

render dot tree to file

Default: False

digraph parallel {
graph [fontname="times-roman"];
node [fontname="times-roman"];
edge [fontname="times-roman"];
Parallel [fillcolor=gold, fontcolor=black, fontsize=11, shape=note, style=filled];
Context [fillcolor=gray, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
Parallel -> Context;
Sequence [fillcolor=orange, fontcolor=black, fontsize=11, shape=box, style=filled];
Parallel -> Sequence;
"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";
}
_images/context_switching.gif
class py_trees.demos.context_switching.ContextSwitch(name: str = 'ContextSwitch')[source]

Bases: Behaviour

An example of a context switching class.

This class sets (in initialise()) and restores a context (in terminate()). Use in parallel with a sequence/subtree that does the work while in this context.

Attention

Simply setting a pair of behaviours (set and reset context) on either end of a sequence will not suffice for context switching. In the case that one of the work behaviours in the sequence fails, the final reset context switch will never trigger.

Parameters

name (str) –

__init__(name: str = 'ContextSwitch')[source]

Initialise with a behaviour name.

Parameters

name (str) –

initialise() None[source]

Backup and set a new context.

Return type

None

terminate(new_status: Status) None[source]

Restore the context with the previously backed up context.

Parameters

new_status (Status) –

Return type

None

update() Status[source]

Just returns RUNNING while it waits for other activities to finish.

Return type

Status

py_trees.demos.context_switching.command_line_argument_parser() ArgumentParser[source]

Process command line arguments.

Returns

the argument parser

Return type

ArgumentParser

py_trees.demos.context_switching.create_root() Behaviour[source]

Create the root behaviour and it’s subtree.

Returns

the root behaviour

Return type

Behaviour

py_trees.demos.context_switching.description() str[source]

Print description and usage information about the program.

Returns

the program description string

Return type

str

py_trees.demos.context_switching.epilog() Optional[str][source]

Print a noodly epilog for –help.

Returns

the noodly message

Return type

Optional[str]

py_trees.demos.context_switching.main() None[source]

Entry point for the demo script.

Return type

None

py_trees/demos/contex_switching.py
  1#!/usr/bin/env python
  2#
  3# License: BSD
  4#   https://raw.githubusercontent.com/splintered-reality/py_trees/devel/LICENSE
  5#
  6##############################################################################
  7# Documentation
  8##############################################################################
  9
 10"""
 11A py_trees demo.
 12
 13.. argparse::
 14   :module: py_trees.demos.context_switching
 15   :func: command_line_argument_parser
 16   :prog: py-trees-demo-context-switching
 17
 18.. graphviz:: dot/demo-context_switching.dot
 19
 20.. image:: images/context_switching.gif
 21"""
 22
 23##############################################################################
 24# Imports
 25##############################################################################
 26
 27import argparse
 28import sys
 29import time
 30import typing
 31
 32import py_trees
 33import py_trees.console as console
 34
 35##############################################################################
 36# Classes
 37##############################################################################
 38
 39
 40def description() -> str:
 41    """
 42    Print description and usage information about the program.
 43
 44    Returns:
 45       the program description string
 46    """
 47    content = "Demonstrates context switching with parallels and sequences.\n"
 48    content += "\n"
 49    content += (
 50        "A context switching behaviour is run in parallel with a work sequence.\n"
 51    )
 52    content += (
 53        "Switching the context occurs in the initialise() and terminate() methods\n"
 54    )
 55    content += (
 56        "of the context switching behaviour. Note that whether the sequence results\n"
 57    )
 58    content += (
 59        "in failure or success, the context switch behaviour will always call the\n"
 60    )
 61    content += (
 62        "terminate() method to restore the context. It will also call terminate()\n"
 63    )
 64    content += (
 65        "to restore the context in the event of a higher priority parent cancelling\n"
 66    )
 67    content += "this parallel subtree.\n"
 68    if py_trees.console.has_colours:
 69        banner_line = console.green + "*" * 79 + "\n" + console.reset
 70        s = banner_line
 71        s += console.bold_white + "Context Switching".center(79) + "\n" + console.reset
 72        s += banner_line
 73        s += "\n"
 74        s += content
 75        s += "\n"
 76        s += banner_line
 77    else:
 78        s = content
 79    return s
 80
 81
 82def epilog() -> typing.Optional[str]:
 83    """
 84    Print a noodly epilog for --help.
 85
 86    Returns:
 87       the noodly message
 88    """
 89    if py_trees.console.has_colours:
 90        return (
 91            console.cyan
 92            + "And his noodly appendage reached forth to tickle the blessed...\n"
 93            + console.reset
 94        )
 95    else:
 96        return None
 97
 98
 99def command_line_argument_parser() -> argparse.ArgumentParser:
100    """
101    Process command line arguments.
102
103    Returns:
104        the argument parser
105    """
106    parser = argparse.ArgumentParser(
107        description=description(),
108        epilog=epilog(),
109        formatter_class=argparse.RawDescriptionHelpFormatter,
110    )
111    parser.add_argument(
112        "-r", "--render", action="store_true", help="render dot tree to file"
113    )
114    return parser
115
116
117class ContextSwitch(py_trees.behaviour.Behaviour):
118    """
119    An example of a context switching class.
120
121    This class sets (in ``initialise()``)
122    and restores a context (in ``terminate()``). Use in parallel with a
123    sequence/subtree that does the work while in this context.
124
125    .. attention:: Simply setting a pair of behaviours (set and reset context) on
126        either end of a sequence will not suffice for context switching. In the case
127        that one of the work behaviours in the sequence fails, the final reset context
128        switch will never trigger.
129    """
130
131    def __init__(self, name: str = "ContextSwitch"):
132        """Initialise with a behaviour name."""
133        super(ContextSwitch, self).__init__(name)
134        self.feedback_message = "no context"
135
136    def initialise(self) -> None:
137        """Backup and set a new context."""
138        self.logger.debug("%s.initialise()[switch context]" % (self.__class__.__name__))
139        # Some actions that:
140        #   1. retrieve the current context from somewhere
141        #   2. cache the context internally
142        #   3. apply a new context
143        self.feedback_message = "new context"
144
145    def update(self) -> py_trees.common.Status:
146        """Just returns RUNNING while it waits for other activities to finish."""
147        self.logger.debug(
148            "%s.update()[RUNNING][%s]"
149            % (self.__class__.__name__, self.feedback_message)
150        )
151        return py_trees.common.Status.RUNNING
152
153    def terminate(self, new_status: py_trees.common.Status) -> None:
154        """Restore the context with the previously backed up context."""
155        self.logger.debug(
156            "%s.terminate()[%s->%s][restore context]"
157            % (self.__class__.__name__, self.status, new_status)
158        )
159        # Some actions that:
160        #   1. restore the cached context
161        self.feedback_message = "restored context"
162
163
164def create_root() -> py_trees.behaviour.Behaviour:
165    """
166    Create the root behaviour and it's subtree.
167
168    Returns:
169        the root behaviour
170    """
171    root = py_trees.composites.Parallel(
172        name="Parallel", policy=py_trees.common.ParallelPolicy.SuccessOnOne()
173    )
174    context_switch = ContextSwitch(name="Context")
175    sequence = py_trees.composites.Sequence(name="Sequence", memory=True)
176    for job in ["Action 1", "Action 2"]:
177        success_after_two = py_trees.behaviours.StatusQueue(
178            name=job,
179            queue=[py_trees.common.Status.RUNNING, py_trees.common.Status.RUNNING],
180            eventually=py_trees.common.Status.SUCCESS,
181        )
182        sequence.add_child(success_after_two)
183    root.add_child(context_switch)
184    root.add_child(sequence)
185    return root
186
187
188##############################################################################
189# Main
190##############################################################################
191
192
193def main() -> None:
194    """Entry point for the demo script."""
195    args = command_line_argument_parser().parse_args()
196    print(description())
197    py_trees.logging.level = py_trees.logging.Level.DEBUG
198
199    root = create_root()
200
201    ####################
202    # Rendering
203    ####################
204    if args.render:
205        py_trees.display.render_dot_tree(root)
206        sys.exit()
207
208    ####################
209    # Execute
210    ####################
211    root.setup_with_descendants()
212    for i in range(1, 6):
213        try:
214            print("\n--------- Tick {0} ---------\n".format(i))
215            root.tick_once()
216            print("\n")
217            print("{}".format(py_trees.display.unicode_tree(root, show_status=True)))
218            time.sleep(1.0)
219        except KeyboardInterrupt:
220            break
221    print("\n")

py-trees-demo-dot-graphs

A py_trees demo.

Renders a dot graph for a simple tree, with blackboxes.

usage: py-trees-demo-dot-graphs [-h]
                                [-l {all,fine_detail,detail,component,big_picture}]

Named Arguments

-l, --level

Possible choices: all, fine_detail, detail, component, big_picture

visibility level

Default: “fine_detail”

digraph demo_dot_graphs_fine_detail {
graph [fontname="times-roman"];
node [fontname="times-roman"];
edge [fontname="times-roman"];
"Demo Dot Graphs fine_detail" [fillcolor=cyan, fontcolor=black, fontsize=11, shape=octagon, style=filled];
"BlackBox 1" [fillcolor=gray20, fontcolor=white, fontsize=11, shape=box, style=filled];
"Demo Dot Graphs fine_detail" -> "BlackBox 1";
Worker [fillcolor=gray, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
"BlackBox 1" -> Worker;
"Worker*" [fillcolor=gray, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
"BlackBox 1" -> "Worker*";
"Worker**" [fillcolor=gray, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
"BlackBox 1" -> "Worker**";
"Blackbox 3" [fillcolor=gray20, fontcolor=dodgerblue, fontsize=11, shape=box, style=filled];
"BlackBox 1" -> "Blackbox 3";
"Worker***" [fillcolor=gray, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
"Blackbox 3" -> "Worker***";
"Worker****" [fillcolor=gray, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
"Blackbox 3" -> "Worker****";
"Worker*****" [fillcolor=gray, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
"Blackbox 3" -> "Worker*****";
"Blackbox 2" [fillcolor=gray20, fontcolor=lawngreen, fontsize=11, shape=box, style=filled];
"Demo Dot Graphs fine_detail" -> "Blackbox 2";
"Worker******" [fillcolor=gray, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
"Blackbox 2" -> "Worker******";
"Worker*******" [fillcolor=gray, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
"Blackbox 2" -> "Worker*******";
"Worker********" [fillcolor=gray, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
"Blackbox 2" -> "Worker********";
}
py_trees.demos.dot_graphs.command_line_argument_parser() ArgumentParser[source]

Process command line arguments.

Returns

the argument parser

Return type

ArgumentParser

py_trees.demos.dot_graphs.create_tree(level: str) Behaviour[source]

Create the root behaviour and it’s subtree.

Returns

the root behaviour

Parameters

level (str) –

Return type

Behaviour

py_trees.demos.dot_graphs.description() str[source]

Print description and usage information about the program.

Returns

the program description string

Return type

str

py_trees.demos.dot_graphs.epilog() Optional[str][source]

Print a noodly epilog for –help.

Returns

the noodly message

Return type

Optional[str]

py_trees.demos.dot_graphs.main() None[source]

Entry point for the demo script.

Return type

None

py_trees/demos/dot_graphs.py
  1#!/usr/bin/env python
  2#
  3# License: BSD
  4#   https://raw.githubusercontent.com/splintered-reality/py_trees/devel/LICENSE
  5#
  6##############################################################################
  7# Documentation
  8##############################################################################
  9
 10"""
 11A py_trees demo.
 12
 13.. argparse::
 14   :module: py_trees.demos.dot_graphs
 15   :func: command_line_argument_parser
 16   :prog: py-trees-demo-dot-graphs
 17
 18.. graphviz:: dot/demo-dot-graphs.dot
 19
 20"""
 21
 22##############################################################################
 23# Imports
 24##############################################################################
 25
 26import argparse
 27import subprocess
 28import typing
 29
 30import py_trees
 31import py_trees.console as console
 32
 33##############################################################################
 34# Classes
 35##############################################################################
 36
 37
 38def description() -> str:
 39    """
 40    Print description and usage information about the program.
 41
 42    Returns:
 43       the program description string
 44    """
 45    name = "py-trees-demo-dot-graphs"
 46    content = "Renders a dot graph for a simple tree, with blackboxes.\n"
 47    if py_trees.console.has_colours:
 48        banner_line = console.green + "*" * 79 + "\n" + console.reset
 49        s = banner_line
 50        s += console.bold_white + "Dot Graphs".center(79) + "\n" + console.reset
 51        s += banner_line
 52        s += "\n"
 53        s += content
 54        s += "\n"
 55        s += console.white
 56        s += console.bold + "    Generate Full Dot Graph" + console.reset + "\n"
 57        s += "\n"
 58        s += console.cyan + "        {0}".format(name) + console.reset + "\n"
 59        s += "\n"
 60        s += console.bold + "    With Varying Visibility Levels" + console.reset + "\n"
 61        s += "\n"
 62        s += (
 63            console.cyan
 64            + "        {0}".format(name)
 65            + console.yellow
 66            + " --level=all"
 67            + console.reset
 68            + "\n"
 69        )
 70        s += (
 71            console.cyan
 72            + "        {0}".format(name)
 73            + console.yellow
 74            + " --level=detail"
 75            + console.reset
 76            + "\n"
 77        )
 78        s += (
 79            console.cyan
 80            + "        {0}".format(name)
 81            + console.yellow
 82            + " --level=component"
 83            + console.reset
 84            + "\n"
 85        )
 86        s += (
 87            console.cyan
 88            + "        {0}".format(name)
 89            + console.yellow
 90            + " --level=big_picture"
 91            + console.reset
 92            + "\n"
 93        )
 94        s += "\n"
 95        s += banner_line
 96    else:
 97        s = content
 98    return s
 99
100
101def epilog() -> typing.Optional[str]:
102    """
103    Print a noodly epilog for --help.
104
105    Returns:
106       the noodly message
107    """
108    if py_trees.console.has_colours:
109        return (
110            console.cyan
111            + "And his noodly appendage reached forth to tickle the blessed...\n"
112            + console.reset
113        )
114    else:
115        return None
116
117
118def command_line_argument_parser() -> argparse.ArgumentParser:
119    """
120    Process command line arguments.
121
122    Returns:
123        the argument parser
124    """
125    parser = argparse.ArgumentParser(
126        description=description(),
127        epilog=epilog(),
128        formatter_class=argparse.RawDescriptionHelpFormatter,
129    )
130    parser.add_argument(
131        "-l",
132        "--level",
133        action="store",
134        default="fine_detail",
135        choices=["all", "fine_detail", "detail", "component", "big_picture"],
136        help="visibility level",
137    )
138    return parser
139
140
141def create_tree(level: str) -> py_trees.behaviour.Behaviour:
142    """
143    Create the root behaviour and it's subtree.
144
145    Returns:
146        the root behaviour
147    """
148    root = py_trees.composites.Selector(name="Demo Dot Graphs %s" % level, memory=False)
149    first_blackbox = py_trees.composites.Sequence(name="BlackBox 1", memory=True)
150    first_blackbox.add_child(py_trees.behaviours.Running("Worker"))
151    first_blackbox.add_child(py_trees.behaviours.Running("Worker"))
152    first_blackbox.add_child(py_trees.behaviours.Running("Worker"))
153    first_blackbox.blackbox_level = py_trees.common.BlackBoxLevel.BIG_PICTURE
154    second_blackbox = py_trees.composites.Sequence(name="Blackbox 2", memory=True)
155    second_blackbox.add_child(py_trees.behaviours.Running("Worker"))
156    second_blackbox.add_child(py_trees.behaviours.Running("Worker"))
157    second_blackbox.add_child(py_trees.behaviours.Running("Worker"))
158    second_blackbox.blackbox_level = py_trees.common.BlackBoxLevel.COMPONENT
159    third_blackbox = py_trees.composites.Sequence(name="Blackbox 3", memory=True)
160    third_blackbox.add_child(py_trees.behaviours.Running("Worker"))
161    third_blackbox.add_child(py_trees.behaviours.Running("Worker"))
162    third_blackbox.add_child(py_trees.behaviours.Running("Worker"))
163    third_blackbox.blackbox_level = py_trees.common.BlackBoxLevel.DETAIL
164    root.add_child(first_blackbox)
165    root.add_child(second_blackbox)
166    first_blackbox.add_child(third_blackbox)
167    return root
168
169
170##############################################################################
171# Main
172##############################################################################
173
174
175def main() -> None:
176    """Entry point for the demo script."""
177    args = command_line_argument_parser().parse_args()
178    args.enum_level = py_trees.common.string_to_visibility_level(args.level)
179    print(description())
180    py_trees.logging.level = py_trees.logging.Level.DEBUG
181
182    root = create_tree(args.level)
183    py_trees.display.render_dot_tree(root, args.enum_level)
184
185    if py_trees.utilities.which("xdot"):
186        try:
187            subprocess.call(["xdot", "demo_dot_graphs_%s.dot" % args.level])
188        except KeyboardInterrupt:
189            pass
190    else:
191        print("")
192        console.logerror(
193            "No xdot viewer found, skipping display [hint: sudo apt install xdot]"
194        )
195        print("")

py-trees-demo-either-or

A py_trees demo.

A demonstration of the ‘either_or’ idiom.

This behaviour tree pattern enables triggering of subtrees with equal priority (first in, first served).

EVENTS

  • 3 : joystick one enabled, task one starts

  • 5 : task one finishes

  • 6 : joystick two enabled, task two starts

  • 7 : joystick one enabled, task one ignored, task two continues

  • 8 : task two finishes

usage: py-trees-demo-either-or [-h] [-r | -i]

Named Arguments

-r, --render

render dot tree to file

Default: False

-i, --interactive

pause and wait for keypress at each tick

Default: False

digraph pastafarianism {
ordering=out;
graph [fontname="times-roman"];
node [fontname="times-roman"];
edge [fontname="times-roman"];
Root [label="Root\n--SuccessOnAll(-)--", shape=parallelogram, style=filled, fillcolor=gold, fontsize=9, fontcolor=black];
Reset [label=Reset, shape=box, style=filled, fillcolor=orange, fontsize=9, fontcolor=black];
Root -> Reset;
"Joy1 - Disabled" [label="Joy1 - Disabled", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Reset -> "Joy1 - Disabled";
"Joy2 - Disabled" [label="Joy2 - Disabled", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Reset -> "Joy2 - Disabled";
"Joy1 Events" [label="Joy1 Events", shape=box, style=filled, fillcolor=orange, fontsize=9, fontcolor=black];
Root -> "Joy1 Events";
FisR [label=FisR, shape=ellipse, style=filled, fillcolor=ghostwhite, fontsize=9, fontcolor=black];
"Joy1 Events" -> FisR;
"Joystick 1" [label="Joystick 1", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
FisR -> "Joystick 1";
"Joy1 - Enabled" [label="Joy1 - Enabled", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
"Joy1 Events" -> "Joy1 - Enabled";
"Joy2 Events" [label="Joy2 Events", shape=box, style=filled, fillcolor=orange, fontsize=9, fontcolor=black];
Root -> "Joy2 Events";
"FisR*" [label="FisR*", shape=ellipse, style=filled, fillcolor=ghostwhite, fontsize=9, fontcolor=black];
"Joy2 Events" -> "FisR*";
"Joystick 2" [label="Joystick 2", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
"FisR*" -> "Joystick 2";
"Joy2 - Enabled" [label="Joy2 - Enabled", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
"Joy2 Events" -> "Joy2 - Enabled";
Tasks [label=Tasks, shape=octagon, style=filled, fillcolor=cyan, fontsize=9, fontcolor=black];
Root -> Tasks;
EitherOr [label=EitherOr, shape=box, style=filled, fillcolor=orange, fontsize=9, fontcolor=black];
Tasks -> EitherOr;
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?";
"Task 1" [label="Task 1", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
"Option 1" -> "Task 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?*";
"Task 2" [label="Task 2", shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
"Option 2" -> "Task 2";
Idle [label=Idle, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Tasks -> Idle;
}
_images/either_or.gif
py_trees.demos.either_or.command_line_argument_parser() ArgumentParser[source]

Process command line arguments.

Returns

the argument parser

Return type

ArgumentParser

py_trees.demos.either_or.create_root() Behaviour[source]

Create the root behaviour and it’s subtree.

Returns

the root behaviour

Return type

Behaviour

py_trees.demos.either_or.description(root: Behaviour) str[source]

Print description and usage information about the program.

Returns

the program description string

Parameters

root (Behaviour) –

Return type

str

py_trees.demos.either_or.epilog() Optional[str][source]

Print a noodly epilog for –help.

Returns

the noodly message

Return type

Optional[str]

py_trees.demos.either_or.main() None[source]

Entry point for the demo script.

Return type

None

py_trees.demos.either_or.post_tick_handler(snapshot_visitor: SnapshotVisitor, behaviour_tree: BehaviourTree) None[source]

Print data about the part of the tree visited.

Parameters
  • snapshot_handler – gather data about the part of the tree visited

  • behaviour_tree (BehaviourTree) – tree to gather data from

  • snapshot_visitor (SnapshotVisitor) –

Return type

None

py_trees.demos.either_or.pre_tick_handler(behaviour_tree: BehaviourTree) None[source]

Print a banner with current tick count prior to ticking the tree.

Parameters

behaviour_tree (BehaviourTree) – the tree to tick (used to fetch the count number)

Return type

None

py_trees/demos/either_or.py
  1#!/usr/bin/env python
  2#
  3# License: BSD
  4#   https://raw.githubusercontent.com/splintered-reality/py_trees/devel/LICENSE
  5#
  6##############################################################################
  7# Documentation
  8##############################################################################
  9
 10"""
 11A py_trees demo.
 12
 13.. argparse::
 14   :module: py_trees.demos.either_or
 15   :func: command_line_argument_parser
 16   :prog: py-trees-demo-either-or
 17
 18.. graphviz:: dot/demo-either-or.dot
 19
 20.. image:: images/either_or.gif
 21"""
 22
 23##############################################################################
 24# Imports
 25##############################################################################
 26
 27import argparse
 28import functools
 29import operator
 30import sys
 31import time
 32import typing
 33
 34import py_trees
 35import py_trees.console as console
 36
 37##############################################################################
 38# Classes
 39##############################################################################
 40
 41
 42def description(root: py_trees.behaviour.Behaviour) -> str:
 43    """
 44    Print description and usage information about the program.
 45
 46    Returns:
 47       the program description string
 48    """
 49    content = "A demonstration of the 'either_or' idiom.\n\n"
 50    content += "This behaviour tree pattern enables triggering of subtrees\n"
 51    content += "with equal priority (first in, first served).\n"
 52    content += "\n"
 53    content += "EVENTS\n"
 54    content += "\n"
 55    content += " -  3 : joystick one enabled, task one starts\n"
 56    content += " -  5 : task one finishes\n"
 57    content += " -  6 : joystick two enabled, task two starts\n"
 58    content += " -  7 : joystick one enabled, task one ignored, task two continues\n"
 59    content += " -  8 : task two finishes\n"
 60    content += "\n"
 61    if py_trees.console.has_colours:
 62        banner_line = console.green + "*" * 79 + "\n" + console.reset
 63        s = banner_line
 64        s += console.bold_white + "Either Or".center(79) + "\n" + console.reset
 65        s += banner_line
 66        s += "\n"
 67        s += content
 68        s += "\n"
 69        s += banner_line
 70    else:
 71        s = content
 72    return s
 73
 74
 75def epilog() -> typing.Optional[str]:
 76    """
 77    Print a noodly epilog for --help.
 78
 79    Returns:
 80       the noodly message
 81    """
 82    if py_trees.console.has_colours:
 83        return (
 84            console.cyan
 85            + "And his noodly appendage reached forth to tickle the blessed...\n"
 86            + console.reset
 87        )
 88    else:
 89        return None
 90
 91
 92def command_line_argument_parser() -> argparse.ArgumentParser:
 93    """
 94    Process command line arguments.
 95
 96    Returns:
 97        the argument parser
 98    """
 99    parser = argparse.ArgumentParser(
100        description=description(create_root()),
101        epilog=epilog(),
102        formatter_class=argparse.RawDescriptionHelpFormatter,
103    )
104    group = parser.add_mutually_exclusive_group()
105    group.add_argument(
106        "-r", "--render", action="store_true", help="render dot tree to file"
107    )
108    group.add_argument(
109        "-i",
110        "--interactive",
111        action="store_true",
112        help="pause and wait for keypress at each tick",
113    )
114    return parser
115
116
117def pre_tick_handler(behaviour_tree: py_trees.trees.BehaviourTree) -> None:
118    """Print a banner with current tick count prior to ticking the tree.
119
120    Args:
121       behaviour_tree: the tree to tick (used to fetch the count number)
122    """
123    print("\n--------- Run %s ---------\n" % behaviour_tree.count)
124
125
126def post_tick_handler(
127    snapshot_visitor: py_trees.visitors.SnapshotVisitor,
128    behaviour_tree: py_trees.trees.BehaviourTree,
129) -> None:
130    """
131    Print data about the part of the tree visited.
132
133    Args:
134        snapshot_handler: gather data about the part of the tree visited
135        behaviour_tree: tree to gather data from
136    """
137    print(
138        "\n"
139        + py_trees.display.unicode_tree(
140            root=behaviour_tree.root,
141            visited=snapshot_visitor.visited,
142            previously_visited=snapshot_visitor.previously_visited,
143        )
144    )
145    print(py_trees.display.unicode_blackboard())
146
147
148def create_root() -> py_trees.behaviour.Behaviour:
149    """
150    Create the root behaviour and it's subtree.
151
152    Returns:
153        the root behaviour
154    """
155    trigger_one = py_trees.decorators.FailureIsRunning(
156        name="FisR", child=py_trees.behaviours.SuccessEveryN(name="Joystick 1", n=4)
157    )
158    trigger_two = py_trees.decorators.FailureIsRunning(
159        name="FisR", child=py_trees.behaviours.SuccessEveryN(name="Joystick 2", n=7)
160    )
161    enable_joystick_one = py_trees.behaviours.SetBlackboardVariable(
162        name="Joy1 - Enabled",
163        variable_name="joystick_one",
164        variable_value="enabled",
165        overwrite=True,
166    )
167    enable_joystick_two = py_trees.behaviours.SetBlackboardVariable(
168        name="Joy2 - Enabled",
169        variable_name="joystick_two",
170        variable_value="enabled",
171        overwrite=True,
172    )
173    reset_joystick_one = py_trees.behaviours.SetBlackboardVariable(
174        name="Joy1 - Disabled",
175        variable_name="joystick_one",
176        variable_value="disabled",
177        overwrite=True,
178    )
179    reset_joystick_two = py_trees.behaviours.SetBlackboardVariable(
180        name="Joy2 - Disabled",
181        variable_name="joystick_two",
182        variable_value="disabled",
183        overwrite=True,
184    )
185    task_one = py_trees.behaviours.TickCounter(
186        name="Task 1", duration=2, completion_status=py_trees.common.Status.SUCCESS
187    )
188    task_two = py_trees.behaviours.TickCounter(
189        name="Task 2", duration=2, completion_status=py_trees.common.Status.SUCCESS
190    )
191    idle = py_trees.behaviours.Running(name="Idle")
192    either_or = py_trees.idioms.either_or(
193        name="Either Or",
194        conditions=[
195            py_trees.common.ComparisonExpression(
196                "joystick_one", "enabled", operator.eq
197            ),
198            py_trees.common.ComparisonExpression(
199                "joystick_two", "enabled", operator.eq
200            ),
201        ],
202        subtrees=[task_one, task_two],
203        namespace="either_or",
204    )
205    root = py_trees.composites.Parallel(
206        name="Root",
207        policy=py_trees.common.ParallelPolicy.SuccessOnAll(synchronise=False),
208    )
209    reset = py_trees.composites.Sequence(name="Reset", memory=True)
210    reset.add_children([reset_joystick_one, reset_joystick_two])
211    joystick_one_events = py_trees.composites.Sequence(name="Joy1 Events", memory=True)
212    joystick_one_events.add_children([trigger_one, enable_joystick_one])
213    joystick_two_events = py_trees.composites.Sequence(name="Joy2 Events", memory=True)
214    joystick_two_events.add_children([trigger_two, enable_joystick_two])
215    tasks = py_trees.composites.Selector(name="Tasks", memory=False)
216    tasks.add_children([either_or, idle])
217    root.add_children([reset, joystick_one_events, joystick_two_events, tasks])
218    return root
219
220
221##############################################################################
222# Main
223##############################################################################
224
225
226def main() -> None:
227    """Entry point for the demo script."""
228    args = command_line_argument_parser().parse_args()
229    # py_trees.logging.level = py_trees.logging.Level.DEBUG
230    root = create_root()
231    print(description(root))
232
233    ####################
234    # Rendering
235    ####################
236    if args.render:
237        py_trees.display.render_dot_tree(root)
238        sys.exit()
239
240    ####################
241    # Tree Stewardship
242    ####################
243    behaviour_tree = py_trees.trees.BehaviourTree(root)
244    behaviour_tree.add_pre_tick_handler(pre_tick_handler)
245    behaviour_tree.visitors.append(py_trees.visitors.DebugVisitor())
246    snapshot_visitor = py_trees.visitors.SnapshotVisitor()
247    behaviour_tree.add_post_tick_handler(
248        functools.partial(post_tick_handler, snapshot_visitor)
249    )
250    behaviour_tree.visitors.append(snapshot_visitor)
251    behaviour_tree.setup(timeout=15)
252
253    ####################
254    # Tick Tock
255    ####################
256    if args.interactive:
257        py_trees.console.read_single_keypress()
258    for _unused_i in range(1, 11):
259        try:
260            behaviour_tree.tick()
261            if args.interactive:
262                py_trees.console.read_single_keypress()
263            else:
264                time.sleep(0.5)
265        except KeyboardInterrupt:
266            break
267    print("\n")

py-trees-demo-eternal-guard

A py_trees demo.

A demonstration of the ‘eternal guard’ concept.

Two binary (F|S) conditional checks will fire every tick, thus providing a fail-fast mechanism for the long running sequence of tasks behind them.

usage: py-trees-demo-eternal-guard [-h] [-r | -i]

Named Arguments

-r, --render

render dot tree to file

Default: False

-i, --interactive

pause and wait for keypress at each tick

Default: False

digraph pastafarianism {
ordering=out;
graph [fontname="times-roman"];
node [fontname="times-roman"];
edge [fontname="times-roman"];
"Eternal Guard" [fillcolor=orange, fontcolor=black, fontsize=9, label="Eternal Guard", shape=box, style=filled];
"Condition 1" [fillcolor=gray, fontcolor=black, fontsize=9, label="Condition 1", shape=ellipse, style=filled];
"Eternal Guard" -> "Condition 1";
"Condition 2" [fillcolor=gray, fontcolor=black, fontsize=9, label="Condition 2", shape=ellipse, style=filled];
"Eternal Guard" -> "Condition 2";
"Task Sequence" [fillcolor=orange, fontcolor=black, fontsize=9, label="Ⓜ Task Sequence", shape=box, style=filled];
"Eternal Guard" -> "Task Sequence";
"Worker 1" [fillcolor=gray, fontcolor=black, fontsize=9, label="Worker 1", shape=ellipse, style=filled];
"Task Sequence" -> "Worker 1";
"Worker 2" [fillcolor=gray, fontcolor=black, fontsize=9, label="Worker 2", shape=ellipse, style=filled];
"Task Sequence" -> "Worker 2";
}
_images/eternal_guard.gif
py_trees.demos.eternal_guard.command_line_argument_parser() ArgumentParser[source]

Process command line arguments.

Returns

the argument parser

Return type

ArgumentParser

py_trees.demos.eternal_guard.create_root() Behaviour[source]

Create the root behaviour and it’s subtree.

Returns

the root behaviour

Return type

Behaviour

py_trees.demos.eternal_guard.description(root: Behaviour) str[source]

Print description and usage information about the program.

Returns

the program description string

Parameters

root (Behaviour) –

Return type

str

py_trees.demos.eternal_guard.epilog() Optional[str][source]

Print a noodly epilog for –help.

Returns

the noodly message

Return type

Optional[str]

py_trees.demos.eternal_guard.main() None[source]

Entry point for the demo script.

Return type

None

py_trees.demos.eternal_guard.post_tick_handler(snapshot_visitor: SnapshotVisitor, behaviour_tree: BehaviourTree) None[source]

Print data about the part of the tree visited.

Parameters
  • snapshot_handler – gather data about the part of the tree visited

  • behaviour_tree (BehaviourTree) – tree to gather data from

  • snapshot_visitor (SnapshotVisitor) –

Return type

None

py_trees.demos.eternal_guard.pre_tick_handler(behaviour_tree: BehaviourTree) None[source]

Print a banner with current tick count prior to ticking the tree.

Parameters

behaviour_tree (BehaviourTree) – the tree to tick (used to fetch the count number)

Return type

None

py_trees/demos/eternal_guard.py
  1#!/usr/bin/env python
  2#
  3# License: BSD
  4#   https://raw.githubusercontent.com/splintered-reality/py_trees/devel/LICENSE
  5#
  6##############################################################################
  7# Documentation
  8##############################################################################
  9
 10"""
 11A py_trees demo.
 12
 13.. argparse::
 14   :module: py_trees.demos.eternal_guard
 15   :func: command_line_argument_parser
 16   :prog: py-trees-demo-eternal-guard
 17
 18.. graphviz:: dot/demo-eternal-guard.dot
 19
 20.. image:: images/eternal_guard.gif
 21"""
 22
 23##############################################################################
 24# Imports
 25##############################################################################
 26
 27import argparse
 28import functools
 29import sys
 30import time
 31import typing
 32
 33import py_trees
 34
 35import py_trees.console as console
 36
 37##############################################################################
 38# Classes
 39##############################################################################
 40
 41
 42def description(root: py_trees.behaviour.Behaviour) -> str:
 43    """
 44    Print description and usage information about the program.
 45
 46    Returns:
 47       the program description string
 48    """
 49    content = "A demonstration of the 'eternal guard' concept.\n\n"
 50    content += "Two binary (F|S) conditional checks will fire every\n"
 51    content += "tick, thus providing a fail-fast mechanism for the\n"
 52    content += "long running sequence of tasks behind them.\n"
 53    content += "\n"
 54    if py_trees.console.has_colours:
 55        banner_line = console.green + "*" * 79 + "\n" + console.reset
 56        s = banner_line
 57        s += console.bold_white + "Eternal Guard".center(79) + "\n" + console.reset
 58        s += banner_line
 59        s += "\n"
 60        s += content
 61        s += "\n"
 62        s += py_trees.display.unicode_tree(root)
 63        s += "\n"
 64        s += banner_line
 65    else:
 66        s = content
 67    return s
 68
 69
 70def epilog() -> typing.Optional[str]:
 71    """
 72    Print a noodly epilog for --help.
 73
 74    Returns:
 75       the noodly message
 76    """
 77    if py_trees.console.has_colours:
 78        return (
 79            console.cyan
 80            + "And his noodly appendage reached forth to tickle the blessed...\n"
 81            + console.reset
 82        )
 83    else:
 84        return None
 85
 86
 87def command_line_argument_parser() -> argparse.ArgumentParser:
 88    """
 89    Process command line arguments.
 90
 91    Returns:
 92        the argument parser
 93    """
 94    parser = argparse.ArgumentParser(
 95        description=description(create_root()),
 96        epilog=epilog(),
 97        formatter_class=argparse.RawDescriptionHelpFormatter,
 98    )
 99    group = parser.add_mutually_exclusive_group()
100    group.add_argument(
101        "-r", "--render", action="store_true", help="render dot tree to file"
102    )
103    group.add_argument(
104        "-i",
105        "--interactive",
106        action="store_true",
107        help="pause and wait for keypress at each tick",
108    )
109    return parser
110
111
112def pre_tick_handler(behaviour_tree: py_trees.trees.BehaviourTree) -> None:
113    """Print a banner with current tick count prior to ticking the tree.
114
115    Args:
116       behaviour_tree: the tree to tick (used to fetch the count number)
117    """
118    print("\n--------- Run %s ---------\n" % behaviour_tree.count)
119
120
121def post_tick_handler(
122    snapshot_visitor: py_trees.visitors.SnapshotVisitor,
123    behaviour_tree: py_trees.trees.BehaviourTree,
124) -> None:
125    """
126    Print data about the part of the tree visited.
127
128    Args:
129        snapshot_handler: gather data about the part of the tree visited
130        behaviour_tree: tree to gather data from
131    """
132    print(
133        "\n"
134        + py_trees.display.unicode_tree(
135            root=behaviour_tree.root,
136            visited=snapshot_visitor.visited,
137            previously_visited=snapshot_visitor.previously_visited,
138        )
139    )
140    print(py_trees.display.unicode_blackboard())
141
142
143def create_root() -> py_trees.behaviour.Behaviour:
144    """
145    Create the root behaviour and it's subtree.
146
147    Returns:
148        the root behaviour
149    """
150    eternal_guard = py_trees.composites.Sequence(name="Eternal Guard", memory=False)
151    condition_one = py_trees.behaviours.StatusQueue(
152        name="Condition 1",
153        queue=[
154            py_trees.common.Status.SUCCESS,
155            py_trees.common.Status.FAILURE,
156            py_trees.common.Status.SUCCESS,
157        ],
158        eventually=py_trees.common.Status.SUCCESS,
159    )
160    condition_two = py_trees.behaviours.StatusQueue(
161        name="Condition 2",
162        queue=[
163            py_trees.common.Status.SUCCESS,
164            py_trees.common.Status.SUCCESS,
165            py_trees.common.Status.FAILURE,
166        ],
167        eventually=py_trees.common.Status.SUCCESS,
168    )
169    task_sequence = py_trees.composites.Sequence(name="Task Sequence", memory=True)
170    task_one = py_trees.behaviours.Success(name="Worker 1")
171    task_two = py_trees.behaviours.Running(name="Worker 2")
172
173    eternal_guard.add_children([condition_one, condition_two, task_sequence])
174    task_sequence.add_children([task_one, task_two])
175    return eternal_guard
176
177
178##############################################################################
179# Main
180##############################################################################
181
182
183def main() -> None:
184    """Entry point for the demo script."""
185    args = command_line_argument_parser().parse_args()
186    # py_trees.logging.level = py_trees.logging.Level.DEBUG
187    root = create_root()
188    print(description(root))
189
190    ####################
191    # Rendering
192    ####################
193    if args.render:
194        py_trees.display.render_dot_tree(root)
195        sys.exit()
196
197    ####################
198    # Tree Stewardship
199    ####################
200    behaviour_tree = py_trees.trees.BehaviourTree(root)
201    behaviour_tree.add_pre_tick_handler(pre_tick_handler)
202    behaviour_tree.visitors.append(py_trees.visitors.DebugVisitor())
203    snapshot_visitor = py_trees.visitors.SnapshotVisitor()
204    behaviour_tree.add_post_tick_handler(
205        functools.partial(post_tick_handler, snapshot_visitor)
206    )
207    behaviour_tree.visitors.append(snapshot_visitor)
208    behaviour_tree.setup(timeout=15)
209
210    ####################
211    # Tick Tock
212    ####################
213    if args.interactive:
214        py_trees.console.read_single_keypress()
215    for _unused_i in range(1, 11):
216        try:
217            behaviour_tree.tick()
218            if args.interactive:
219                py_trees.console.read_single_keypress()
220            else:
221                time.sleep(0.5)
222        except KeyboardInterrupt:
223            break
224    print("\n")

py-trees-demo-logging

A py_trees demo.

A demonstration of logging with trees.

This demo utilises a SnapshotVisitor to trigger a post-tick handler to dump a serialisation of the tree to a json log file.

This coupling of visitor and post-tick handler can be used for any kind of event handling - the visitor is the trigger and the post-tick handler the action. Aside from logging, the most common use case is to serialise the tree for messaging to a graphical, runtime monitor.

usage: py-trees-demo-logging [-h] [-r | -i]

Named Arguments

-r, --render

render dot tree to file

Default: False

-i, --interactive

pause and wait for keypress at each tick

Default: False

digraph pastafarianism {
graph [fontname="times-roman"];
node [fontname="times-roman"];
edge [fontname="times-roman"];
Logging [label=Logging, shape=octagon, style=filled, fillcolor=cyan, fontsize=9, fontcolor=black];
EveryN [label=EveryN, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Logging -> EveryN;
Sequence [label=Sequence, shape=box, style=filled, fillcolor=gray20, fontsize=9, fontcolor=lawngreen];
Logging -> Sequence;
Guard [label=Guard, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Sequence -> Guard;
Periodic [label=Periodic, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Sequence -> Periodic;
Finisher [label=Finisher, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Sequence -> Finisher;
Idle [label=Idle, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Logging -> Idle;
}
_images/logging.gif
py_trees.demos.logging.command_line_argument_parser() ArgumentParser[source]

Process command line arguments.

Returns

the argument parser

Return type

ArgumentParser

py_trees.demos.logging.create_tree() Behaviour[source]

Create the root behaviour and it’s subtree.

Returns

the root behaviour

Return type

Behaviour

py_trees.demos.logging.description(root: Behaviour) str[source]

Print description and usage information about the program.

Returns

the program description string

Parameters

root (Behaviour) –

Return type

str

py_trees.demos.logging.epilog() Optional[str][source]

Print a noodly epilog for –help.

Returns

the noodly message

Return type

Optional[str]

py_trees.demos.logging.logger(snapshot_visitor: DisplaySnapshotVisitor, behaviour_tree: BehaviourTree) None[source]

Log the tree (relevant parts thereof) to a yaml file.

Use as a post-tick handler for a tree.

Parameters
Return type

None

py_trees.demos.logging.main() None[source]

Entry point for the demo script.

Return type

None

py_trees/demos/logging.py
  1#!/usr/bin/env python
  2#
  3# License: BSD
  4#   https://raw.githubusercontent.com/splintered-reality/py_trees/devel/LICENSE
  5#
  6##############################################################################
  7# Documentation
  8##############################################################################
  9
 10"""
 11A py_trees demo.
 12
 13.. argparse::
 14   :module: py_trees.demos.logging
 15   :func: command_line_argument_parser
 16   :prog: py-trees-demo-logging
 17
 18.. graphviz:: dot/demo-logging.dot
 19
 20.. image:: images/logging.gif
 21"""
 22
 23##############################################################################
 24# Imports
 25##############################################################################
 26
 27import argparse
 28import functools
 29import json
 30import sys
 31import time
 32import typing
 33
 34import py_trees
 35import py_trees.console as console
 36
 37##############################################################################
 38# Classes
 39##############################################################################
 40
 41
 42def description(root: py_trees.behaviour.Behaviour) -> str:
 43    """
 44    Print description and usage information about the program.
 45
 46    Returns:
 47       the program description string
 48    """
 49    content = "A demonstration of logging with trees.\n\n"
 50    content += "This demo utilises a SnapshotVisitor to trigger\n"
 51    content += "a post-tick handler to dump a serialisation of the\n"
 52    content += "tree to a json log file.\n"
 53    content += "\n"
 54    content += "This coupling of visitor and post-tick handler can be\n"
 55    content += "used for any kind of event handling - the visitor is the\n"
 56    content += "trigger and the post-tick handler the action. Aside from\n"
 57    content += "logging, the most common use case is to serialise the tree\n"
 58    content += "for messaging to a graphical, runtime monitor.\n"
 59    content += "\n"
 60    if py_trees.console.has_colours:
 61        banner_line = console.green + "*" * 79 + "\n" + console.reset
 62        s = banner_line
 63        s += console.bold_white + "Logging".center(79) + "\n" + console.reset
 64        s += banner_line
 65        s += "\n"
 66        s += content
 67        s += "\n"
 68        s += py_trees.display.unicode_tree(root)
 69        s += "\n"
 70        s += banner_line
 71    else:
 72        s = content
 73    return s
 74
 75
 76def epilog() -> typing.Optional[str]:
 77    """
 78    Print a noodly epilog for --help.
 79
 80    Returns:
 81       the noodly message
 82    """
 83    if py_trees.console.has_colours:
 84        return (
 85            console.cyan
 86            + "And his noodly appendage reached forth to tickle the blessed...\n"
 87            + console.reset
 88        )
 89    else:
 90        return None
 91
 92
 93def command_line_argument_parser() -> argparse.ArgumentParser:
 94    """
 95    Process command line arguments.
 96
 97    Returns:
 98        the argument parser
 99    """
100    parser = argparse.ArgumentParser(
101        description=description(create_tree()),
102        epilog=epilog(),
103        formatter_class=argparse.RawDescriptionHelpFormatter,
104    )
105    group = parser.add_mutually_exclusive_group()
106    group.add_argument(
107        "-r", "--render", action="store_true", help="render dot tree to file"
108    )
109    group.add_argument(
110        "-i",
111        "--interactive",
112        action="store_true",
113        help="pause and wait for keypress at each tick",
114    )
115    return parser
116
117
118def logger(
119    snapshot_visitor: py_trees.visitors.DisplaySnapshotVisitor,
120    behaviour_tree: py_trees.trees.BehaviourTree,
121) -> None:
122    """Log the tree (relevant parts thereof) to a yaml file.
123
124    Use as a post-tick handler for a tree.
125    """
126    if snapshot_visitor.changed:
127        print(console.cyan + "Logging.......................yes\n" + console.reset)
128        tree_serialisation = {"tick": behaviour_tree.count, "nodes": []}
129        for node in behaviour_tree.root.iterate():
130            node_type_str = "Behaviour"
131            for behaviour_type in [
132                py_trees.composites.Sequence,
133                py_trees.composites.Selector,
134                py_trees.composites.Parallel,
135                py_trees.decorators.Decorator,
136            ]:
137                if isinstance(node, behaviour_type):
138                    node_type_str = behaviour_type.__name__
139            if node.tip() is not None:
140                node_tip = node.tip()
141                assert node_tip is not None  # help mypy
142                node_tip_id = str(node_tip.id)
143            else:
144                node_tip_id = "none"
145            node_snapshot = {
146                "name": node.name,
147                "id": str(node.id),
148                "parent_id": str(node.parent.id) if node.parent else "none",
149                "child_ids": [str(child.id) for child in node.children],
150                "tip_id": node_tip_id,
151                "class_name": str(node.__module__) + "." + str(type(node).__name__),
152                "type": node_type_str,
153                "status": node.status.value,
154                "message": node.feedback_message,
155                "is_active": True if node.id in snapshot_visitor.visited else False,
156            }
157            typing.cast(list, tree_serialisation["nodes"]).append(node_snapshot)
158        if behaviour_tree.count == 0:
159            with open("dump.json", "w+") as outfile:
160                json.dump(tree_serialisation, outfile, indent=4)
161        else:
162            with open("dump.json", "a") as outfile:
163                json.dump(tree_serialisation, outfile, indent=4)
164    else:
165        print(console.yellow + "Logging.......................no\n" + console.reset)
166
167
168def create_tree() -> py_trees.behaviour.Behaviour:
169    """
170    Create the root behaviour and it's subtree.
171
172    Returns:
173        the root behaviour
174    """
175    every_n_success = py_trees.behaviours.SuccessEveryN("EveryN", 5)
176    sequence = py_trees.composites.Sequence(name="Sequence", memory=True)
177    guard = py_trees.behaviours.Success("Guard")
178    periodic_success = py_trees.behaviours.Periodic("Periodic", 3)
179    finisher = py_trees.behaviours.Success("Finisher")
180    sequence.add_child(guard)
181    sequence.add_child(periodic_success)
182    sequence.add_child(finisher)
183    sequence.blackbox_level = py_trees.common.BlackBoxLevel.COMPONENT
184    idle = py_trees.behaviours.Success("Idle")
185    root = py_trees.composites.Selector(name="Logging", memory=False)
186    root.add_child(every_n_success)
187    root.add_child(sequence)
188    root.add_child(idle)
189    return root
190
191
192##############################################################################
193# Main
194##############################################################################
195
196
197def main() -> None:
198    """Entry point for the demo script."""
199    args = command_line_argument_parser().parse_args()
200    py_trees.logging.level = py_trees.logging.Level.DEBUG
201    tree = create_tree()
202    print(description(tree))
203
204    ####################
205    # Rendering
206    ####################
207    if args.render:
208        py_trees.display.render_dot_tree(tree)
209        sys.exit()
210
211    ####################
212    # Tree Stewardship
213    ####################
214    behaviour_tree = py_trees.trees.BehaviourTree(tree)
215
216    debug_visitor = py_trees.visitors.DebugVisitor()
217    snapshot_visitor = py_trees.visitors.DisplaySnapshotVisitor()
218
219    behaviour_tree.visitors.append(debug_visitor)
220    behaviour_tree.visitors.append(snapshot_visitor)
221
222    behaviour_tree.add_post_tick_handler(functools.partial(logger, snapshot_visitor))
223
224    behaviour_tree.setup(timeout=15)
225
226    ####################
227    # Tick Tock
228    ####################
229    if args.interactive:
230        py_trees.console.read_single_keypress()
231    while True:
232        try:
233            behaviour_tree.tick()
234            if args.interactive:
235                py_trees.console.read_single_keypress()
236            else:
237                time.sleep(0.5)
238        except KeyboardInterrupt:
239            break
240    print("\n")

py-trees-demo-selector

A py_trees demo.

Higher priority switching and interruption in the children of a selector.

In this example the higher priority child is setup to fail initially, falling back to the continually running second child. On the third tick, the first child succeeds and cancels the hitherto running child.

usage: py-trees-demo-selector [-h] [-r]

Named Arguments

-r, --render

render dot tree to file

Default: False

digraph selector {
graph [fontname="times-roman"];
node [fontname="times-roman"];
edge [fontname="times-roman"];
Selector [fillcolor=cyan, fontcolor=black, fontsize=11, shape=octagon, style=filled];
"After Two" [fillcolor=gray, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
Selector -> "After Two";
Running [fillcolor=gray, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
Selector -> Running;
}
_images/selector.gif
py_trees.demos.selector.command_line_argument_parser() ArgumentParser[source]

Process command line arguments.

Returns

the argument parser

Return type

ArgumentParser

py_trees.demos.selector.create_root() Behaviour[source]

Create the root behaviour and it’s subtree.

Returns

the root behaviour

Return type

Behaviour

py_trees.demos.selector.description() str[source]

Print description and usage information about the program.

Returns

the program description string

Return type

str

py_trees.demos.selector.epilog() Optional[str][source]

Print a noodly epilog for –help.

Returns

the noodly message

Return type

Optional[str]

py_trees.demos.selector.main() None[source]

Entry point for the demo script.

Return type

None

py_trees/demos/selector.py
  1#!/usr/bin/env python
  2#
  3# License: BSD
  4#   https://raw.githubusercontent.com/splintered-reality/py_trees/devel/LICENSE
  5#
  6##############################################################################
  7# Documentation
  8##############################################################################
  9
 10"""
 11A py_trees demo.
 12
 13.. argparse::
 14   :module: py_trees.demos.selector
 15   :func: command_line_argument_parser
 16   :prog: py-trees-demo-selector
 17
 18.. graphviz:: dot/demo-selector.dot
 19
 20.. image:: images/selector.gif
 21
 22"""
 23##############################################################################
 24# Imports
 25##############################################################################
 26
 27import argparse
 28import sys
 29import time
 30import typing
 31
 32import py_trees
 33import py_trees.console as console
 34
 35##############################################################################
 36# Classes
 37##############################################################################
 38
 39
 40def description() -> str:
 41    """
 42    Print description and usage information about the program.
 43
 44    Returns:
 45       the program description string
 46    """
 47    content = (
 48        "Higher priority switching and interruption in the children of a selector.\n"
 49    )
 50    content += "\n"
 51    content += "In this example the higher priority child is setup to fail initially,\n"
 52    content += "falling back to the continually running second child. On the third\n"
 53    content += (
 54        "tick, the first child succeeds and cancels the hitherto running child.\n"
 55    )
 56    if py_trees.console.has_colours:
 57        banner_line = console.green + "*" * 79 + "\n" + console.reset
 58        s = banner_line
 59        s += console.bold_white + "Selectors".center(79) + "\n" + console.reset
 60        s += banner_line
 61        s += "\n"
 62        s += content
 63        s += "\n"
 64        s += banner_line
 65    else:
 66        s = content
 67    return s
 68
 69
 70def epilog() -> typing.Optional[str]:
 71    """
 72    Print a noodly epilog for --help.
 73
 74    Returns:
 75       the noodly message
 76    """
 77    if py_trees.console.has_colours:
 78        return (
 79            console.cyan
 80            + "And his noodly appendage reached forth to tickle the blessed...\n"
 81            + console.reset
 82        )
 83    else:
 84        return None
 85
 86
 87def command_line_argument_parser() -> argparse.ArgumentParser:
 88    """
 89    Process command line arguments.
 90
 91    Returns:
 92        the argument parser
 93    """
 94    parser = argparse.ArgumentParser(
 95        description=description(),
 96        epilog=epilog(),
 97        formatter_class=argparse.RawDescriptionHelpFormatter,
 98    )
 99    parser.add_argument(
100        "-r", "--render", action="store_true", help="render dot tree to file"
101    )
102    return parser
103
104
105def create_root() -> py_trees.behaviour.Behaviour:
106    """
107    Create the root behaviour and it's subtree.
108
109    Returns:
110        the root behaviour
111    """
112    root = py_trees.composites.Selector(name="Selector", memory=False)
113    ffs = py_trees.behaviours.StatusQueue(
114        name="FFS",
115        queue=[
116            py_trees.common.Status.FAILURE,
117            py_trees.common.Status.FAILURE,
118            py_trees.common.Status.SUCCESS,
119        ],
120        eventually=py_trees.common.Status.SUCCESS,
121    )
122    always_running = py_trees.behaviours.Running(name="Running")
123    root.add_children([ffs, always_running])
124    return root
125
126
127##############################################################################
128# Main
129##############################################################################
130
131
132def main() -> None:
133    """Entry point for the demo script."""
134    args = command_line_argument_parser().parse_args()
135    print(description())
136    py_trees.logging.level = py_trees.logging.Level.DEBUG
137
138    root = create_root()
139
140    ####################
141    # Rendering
142    ####################
143    if args.render:
144        py_trees.display.render_dot_tree(root)
145        sys.exit()
146
147    ####################
148    # Execute
149    ####################
150    root.setup_with_descendants()
151    for i in range(1, 4):
152        try:
153            print("\n--------- Tick {0} ---------\n".format(i))
154            root.tick_once()
155            print("\n")
156            print(py_trees.display.unicode_tree(root=root, show_status=True))
157            time.sleep(1.0)
158        except KeyboardInterrupt:
159            break
160    print("\n")

py-trees-demo-sequence

A py_trees demo.

Demonstrates sequences in action.

A sequence is populated with 2-tick jobs that are allowed to run through to completion.

usage: py-trees-demo-sequence [-h] [-r]

Named Arguments

-r, --render

render dot tree to file

Default: False

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];
"Job 1" [fillcolor=gray, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
Sequence -> "Job 1";
"Job 2" [fillcolor=gray, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
Sequence -> "Job 2";
"Job 3" [fillcolor=gray, fontcolor=black, fontsize=11, shape=ellipse, style=filled];
Sequence -> "Job 3";
}
_images/sequence.gif
py_trees.demos.sequence.command_line_argument_parser() ArgumentParser[source]

Process command line arguments.

Returns

the argument parser

Return type

ArgumentParser

py_trees.demos.sequence.create_root() Behaviour[source]

Create the root behaviour and it’s subtree.

Returns

the root behaviour

Return type

Behaviour

py_trees.demos.sequence.description() str[source]

Print description and usage information about the program.

Returns

the program description string

Return type

str

py_trees.demos.sequence.epilog() Optional[str][source]

Print a noodly epilog for –help.

Returns

the noodly message

Return type

Optional[str]

py_trees.demos.sequence.main() None[source]

Entry point for the demo script.

Return type

None

py_trees/demos/sequence.py
  1#!/usr/bin/env python
  2#
  3# License: BSD
  4#   https://raw.githubusercontent.com/splintered-reality/py_trees/devel/LICENSE
  5#
  6##############################################################################
  7# Documentation
  8##############################################################################
  9
 10"""
 11A py_trees demo.
 12
 13.. argparse::
 14   :module: py_trees.demos.sequence
 15   :func: command_line_argument_parser
 16   :prog: py-trees-demo-sequence
 17
 18.. graphviz:: dot/demo-sequence.dot
 19
 20.. image:: images/sequence.gif
 21"""
 22
 23##############################################################################
 24# Imports
 25##############################################################################
 26
 27import argparse
 28import sys
 29import time
 30import typing
 31
 32import py_trees
 33import py_trees.console as console
 34
 35##############################################################################
 36# Classes
 37##############################################################################
 38
 39
 40def description() -> str:
 41    """
 42    Print description and usage information about the program.
 43
 44    Returns:
 45       the program description string
 46    """
 47    content = "Demonstrates sequences in action.\n\n"
 48    content += (
 49        "A sequence is populated with 2-tick jobs that are allowed to run through to\n"
 50    )
 51    content += "completion.\n"
 52
 53    if py_trees.console.has_colours:
 54        banner_line = console.green + "*" * 79 + "\n" + console.reset
 55        s = banner_line
 56        s += console.bold_white + "Sequences".center(79) + "\n" + console.reset
 57        s += banner_line
 58        s += "\n"
 59        s += content
 60        s += "\n"
 61        s += banner_line
 62    else:
 63        s = content
 64    return s
 65
 66
 67def epilog() -> typing.Optional[str]:
 68    """
 69    Print a noodly epilog for --help.
 70
 71    Returns:
 72       the noodly message
 73    """
 74    if py_trees.console.has_colours:
 75        return (
 76            console.cyan
 77            + "And his noodly appendage reached forth to tickle the blessed...\n"
 78            + console.reset
 79        )
 80    else:
 81        return None
 82
 83
 84def command_line_argument_parser() -> argparse.ArgumentParser:
 85    """
 86    Process command line arguments.
 87
 88    Returns:
 89        the argument parser
 90    """
 91    parser = argparse.ArgumentParser(
 92        description=description(),
 93        epilog=epilog(),
 94        formatter_class=argparse.RawDescriptionHelpFormatter,
 95    )
 96    parser.add_argument(
 97        "-r", "--render", action="store_true", help="render dot tree to file"
 98    )
 99    return parser
100
101
102def create_root() -> py_trees.behaviour.Behaviour:
103    """
104    Create the root behaviour and it's subtree.
105
106    Returns:
107        the root behaviour
108    """
109    root = py_trees.composites.Sequence(name="Sequence", memory=True)
110    for action in ["Action 1", "Action 2", "Action 3"]:
111        rssss = py_trees.behaviours.StatusQueue(
112            name=action,
113            queue=[
114                py_trees.common.Status.RUNNING,
115                py_trees.common.Status.SUCCESS,
116            ],
117            eventually=py_trees.common.Status.SUCCESS,
118        )
119        root.add_child(rssss)
120    return root
121
122
123##############################################################################
124# Main
125##############################################################################
126
127
128def main() -> None:
129    """Entry point for the demo script."""
130    args = command_line_argument_parser().parse_args()
131    print(description())
132    py_trees.logging.level = py_trees.logging.Level.DEBUG
133
134    root = create_root()
135
136    ####################
137    # Rendering
138    ####################
139    if args.render:
140        py_trees.display.render_dot_tree(root)
141        sys.exit()
142
143    ####################
144    # Execute
145    ####################
146    root.setup_with_descendants()
147    for i in range(1, 6):
148        try:
149            print("\n--------- Tick {0} ---------\n".format(i))
150            root.tick_once()
151            print("\n")
152            print(py_trees.display.unicode_tree(root=root, show_status=True))
153            time.sleep(1.0)
154        except KeyboardInterrupt:
155            break
156    print("\n")

py-trees-demo-tree-stewardship

A py_trees demo.

A demonstration of tree stewardship.

A slightly less trivial tree that uses a simple stdout pre-tick handler and both the debug and snapshot visitors for logging and displaying the state of the tree.

EVENTS

  • 3 : sequence switches from running to success

  • 4 : selector’s first child flicks to success once only

  • 8 : the fallback idler kicks in as everything else fails

  • 14 : the first child kicks in again, aborting a running sequence behind it

usage: py-trees-demo-tree-stewardship [-h]
                                      [-r | --render-with-blackboard-variables | -i]

Named Arguments

-r, --render

render dot tree to file

Default: False

--render-with-blackboard-variables

render dot tree to file with blackboard variables

Default: False

-i, --interactive

pause and wait for keypress at each tick

Default: False

digraph pastafarianism {
graph [fontname="times-roman"];
node [fontname="times-roman"];
edge [fontname="times-roman"];
"Demo Tree" [label="Demo Tree", shape=octagon, style=filled, fillcolor=cyan, fontsize=9, fontcolor=black];
EveryN [label=EveryN, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
"Demo Tree" -> EveryN;
Sequence [label=Sequence, shape=box, style=filled, fillcolor=orange, fontsize=9, fontcolor=black];
"Demo Tree" -> Sequence;
Guard [label=Guard, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Sequence -> Guard;
Periodic [label=Periodic, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Sequence -> Periodic;
Finisher [label=Finisher, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Sequence -> Finisher;
subgraph  {
label=children_of_Sequence;
rank=same;
Guard [label=Guard, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Periodic [label=Periodic, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Finisher [label=Finisher, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
}

Idle [label=Idle, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
"Demo Tree" -> Idle;
subgraph  {
label="children_of_Demo Tree";
rank=same;
EveryN [label=EveryN, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
Sequence [label=Sequence, shape=box, style=filled, fillcolor=orange, fontsize=9, fontcolor=black];
Idle [label=Idle, shape=ellipse, style=filled, fillcolor=gray, fontsize=9, fontcolor=black];
}

count [label="count: -", shape=box, style=filled, color=blue, fillcolor=white, fontsize=8, fontcolor=blue, width=0, height=0, fixedsize=False];
count -> Finisher  [color=blue, constraint=False];
EveryN -> count  [color=blue, constraint=True];
period [label="period: -", shape=box, style=filled, color=blue, fillcolor=white, fontsize=8, fontcolor=blue, width=0, height=0, fixedsize=False];
period -> Finisher  [color=blue, constraint=False];
Periodic -> period  [color=blue, constraint=True];
}
_images/tree_stewardship.gif
class py_trees.demos.stewardship.Finisher[source]

Bases: Behaviour

Gathers blackboard data from other behaviours and prints a summary.

To be used at the end of the tree run.

__init__() None[source]

Set up the blackboard.

Return type

None

update() Status[source]

Fetch blackboard variables and print a summary.

Returns

Always returns SUCCESS.

Return type

Status

class py_trees.demos.stewardship.PeriodicSuccess[source]

Bases: Periodic

Write the period from Periodic as a variable on the blackboard.

__init__() None[source]

Initialise Periodic and set up the blackboard.

Return type

None

update() Status[source]

Run the Periodic update and write to blackboard.

Returns

Returns the Status of Periodic

Return type

Status

class py_trees.demos.stewardship.SuccessEveryN[source]

Bases: SuccessEveryN

Add a blackboard counter to SuccessEveryN.

__init__() None[source]

Set up the blackboard.

Return type

None

update() Status[source]

Run the SuccessEveryN update and write to blackboard.

Returns

Returns the Status of SuccessEveryN

Return type

Status

py_trees.demos.stewardship.command_line_argument_parser() ArgumentParser[source]

Process command line arguments.

Returns

the argument parser

Return type

ArgumentParser

py_trees.demos.stewardship.create_tree() Behaviour[source]

Create the root behaviour and it’s subtree.

Returns

the root behaviour

Return type

Behaviour

py_trees.demos.stewardship.description() str[source]

Print description and usage information about the program.

Returns

the program description string

Return type

str

py_trees.demos.stewardship.epilog() Optional[str][source]

Print a noodly epilog for –help.

Returns

the noodly message

Return type

Optional[str]

py_trees.demos.stewardship.main() None[source]

Entry point for the demo script.

Return type

None

py_trees.demos.stewardship.pre_tick_handler(behaviour_tree: BehaviourTree) None[source]

Generate a simple pre-tick banner printing to stdout.

Parameters

behaviour_tree (BehaviourTree) –

Return type

None

py_trees/demos/stewardship.py
  1#!/usr/bin/env python
  2#
  3# License: BSD
  4#   https://raw.githubusercontent.com/splintered-reality/py_trees/devel/LICENSE
  5#
  6##############################################################################
  7# Documentation
  8##############################################################################
  9
 10"""
 11A py_trees demo.
 12
 13.. argparse::
 14   :module: py_trees.demos.stewardship
 15   :func: command_line_argument_parser
 16   :prog: py-trees-demo-tree-stewardship
 17
 18.. graphviz:: dot/demo-tree-stewardship.dot
 19
 20.. image:: images/tree_stewardship.gif
 21"""
 22
 23##############################################################################
 24# Imports
 25##############################################################################
 26
 27import argparse
 28import sys
 29import time
 30import typing
 31
 32import py_trees
 33import py_trees.console as console
 34
 35##############################################################################
 36# Classes
 37##############################################################################
 38
 39
 40def description() -> str:
 41    """
 42    Print description and usage information about the program.
 43
 44    Returns:
 45       the program description string
 46    """
 47    content = "A demonstration of tree stewardship.\n\n"
 48    content += (
 49        "A slightly less trivial tree that uses a simple stdout pre-tick handler\n"
 50    )
 51    content += "and both the debug and snapshot visitors for logging and displaying\n"
 52    content += "the state of the tree.\n"
 53    content += "\n"
 54    content += "EVENTS\n"
 55    content += "\n"
 56    content += " -  3 : sequence switches from running to success\n"
 57    content += " -  4 : selector's first child flicks to success once only\n"
 58    content += " -  8 : the fallback idler kicks in as everything else fails\n"
 59    content += " - 14 : the first child kicks in again, aborting a running sequence behind it\n"
 60    content += "\n"
 61    if py_trees.console.has_colours:
 62        banner_line = console.green + "*" * 79 + "\n" + console.reset
 63        s = banner_line
 64        s += console.bold_white + "Trees".center(79) + "\n" + console.reset
 65        s += banner_line
 66        s += "\n"
 67        s += content
 68        s += "\n"
 69        s += banner_line
 70    else:
 71        s = content
 72    return s
 73
 74
 75def epilog() -> typing.Optional[str]:
 76    """
 77    Print a noodly epilog for --help.
 78
 79    Returns:
 80       the noodly message
 81    """
 82    if py_trees.console.has_colours:
 83        return (
 84            console.cyan
 85            + "And his noodly appendage reached forth to tickle the blessed...\n"
 86            + console.reset
 87        )
 88    else:
 89        return None
 90
 91
 92def command_line_argument_parser() -> argparse.ArgumentParser:
 93    """
 94    Process command line arguments.
 95
 96    Returns:
 97        the argument parser
 98    """
 99    parser = argparse.ArgumentParser(
100        description=description(),
101        epilog=epilog(),
102        formatter_class=argparse.RawDescriptionHelpFormatter,
103    )
104    group = parser.add_mutually_exclusive_group()
105    group.add_argument(
106        "-r", "--render", action="store_true", help="render dot tree to file"
107    )
108    group.add_argument(
109        "--render-with-blackboard-variables",
110        action="store_true",
111        help="render dot tree to file with blackboard variables",
112    )
113    group.add_argument(
114        "-i",
115        "--interactive",
116        action="store_true",
117        help="pause and wait for keypress at each tick",
118    )
119    return parser
120
121
122def pre_tick_handler(behaviour_tree: py_trees.trees.BehaviourTree) -> None:
123    """Generate a simple pre-tick banner printing to stdout."""
124    print("\n--------- Run %s ---------\n" % behaviour_tree.count)
125
126
127class SuccessEveryN(py_trees.behaviours.SuccessEveryN):
128    """Add a blackboard counter to :class:`~py_trees.behaviours.SuccessEveryN`."""
129
130    def __init__(self) -> None:
131        """Set up the blackboard."""
132        super().__init__(name="EveryN", n=5)
133        self.blackboard = self.attach_blackboard_client(name=self.name)
134        self.blackboard.register_key("count", access=py_trees.common.Access.WRITE)
135
136    def update(self) -> py_trees.common.Status:
137        """
138        Run the :class:`~py_trees.behaviours.SuccessEveryN` update and write to blackboard.
139
140        Returns:
141            Returns the :class:`~py_trees.common.Status` of :class:`~py_trees.behaviours.SuccessEveryN`
142        """
143        status = super().update()
144        self.blackboard.count = self.count
145        return status
146
147
148class PeriodicSuccess(py_trees.behaviours.Periodic):
149    """Write the period from :class:`~py_trees.behaviours.Periodic` as a variable on the blackboard."""
150
151    def __init__(self) -> None:
152        """Initialise :class:`~py_trees.behaviours.Periodic` and set up the blackboard."""
153        super().__init__(name="Periodic", n=3)
154        self.blackboard = self.attach_blackboard_client(name=self.name)
155        self.blackboard.register_key("period", access=py_trees.common.Access.WRITE)
156
157    def update(self) -> py_trees.common.Status:
158        """
159        Run the :class:`~py_trees.behaviours.Periodic` update and write to blackboard.
160
161        Returns:
162            Returns the :class:`~py_trees.common.Status` of :class:`~py_trees.behaviours.Periodic`
163        """
164        status = super().update()
165        self.blackboard.period = self.period
166        return status
167
168
169class Finisher(py_trees.behaviour.Behaviour):
170    """
171    Gathers blackboard data from other behaviours and prints a summary.
172
173    To be used at the end of the tree run.
174    """
175
176    def __init__(self) -> None:
177        """Set up the blackboard."""
178        super().__init__(name="Finisher")
179        self.blackboard = self.attach_blackboard_client(name=self.name)
180        self.blackboard.register_key("count", access=py_trees.common.Access.READ)
181        self.blackboard.register_key("period", access=py_trees.common.Access.READ)
182
183    def update(self) -> py_trees.common.Status:
184        """
185        Fetch blackboard variables and print a summary.
186
187        Returns:
188            Always returns :data:`~py_trees.common.Status.SUCCESS`.
189        """
190        print(console.green + "---------------------------" + console.reset)
191        print(console.bold + "        Finisher" + console.reset)
192        print(
193            console.green + "  Count : {}".format(self.blackboard.count) + console.reset
194        )
195        print(
196            console.green
197            + "  Period: {}".format(self.blackboard.period)
198            + console.reset
199        )
200        print(console.green + "---------------------------" + console.reset)
201        return py_trees.common.Status.SUCCESS
202
203
204def create_tree() -> py_trees.behaviour.Behaviour:
205    """
206    Create the root behaviour and it's subtree.
207
208    Returns:
209        the root behaviour
210    """
211    every_n_success = SuccessEveryN()
212    sequence = py_trees.composites.Sequence(name="Sequence", memory=True)
213    guard = py_trees.behaviours.Success("Guard")
214    periodic_success = PeriodicSuccess()
215    finisher = Finisher()
216    sequence.add_child(guard)
217    sequence.add_child(periodic_success)
218    sequence.add_child(finisher)
219    idle = py_trees.behaviours.Success("Idle")
220    root = py_trees.composites.Selector(name="Demo Tree", memory=False)
221    root.add_child(every_n_success)
222    root.add_child(sequence)
223    root.add_child(idle)
224    return root
225
226
227##############################################################################
228# Main
229##############################################################################
230
231
232def main() -> None:
233    """Entry point for the demo script."""
234    args = command_line_argument_parser().parse_args()
235    py_trees.logging.level = py_trees.logging.Level.DEBUG
236    tree = create_tree()
237    print(description())
238
239    ####################
240    # Rendering
241    ####################
242    if args.render:
243        py_trees.display.render_dot_tree(tree)
244        sys.exit()
245
246    if args.render_with_blackboard_variables:
247        py_trees.display.render_dot_tree(tree, with_blackboard_variables=True)
248        sys.exit()
249
250    ####################
251    # Tree Stewardship
252    ####################
253    py_trees.blackboard.Blackboard.enable_activity_stream(100)
254    behaviour_tree = py_trees.trees.BehaviourTree(tree)
255    behaviour_tree.add_pre_tick_handler(pre_tick_handler)
256    behaviour_tree.visitors.append(py_trees.visitors.DebugVisitor())
257    behaviour_tree.visitors.append(
258        py_trees.visitors.DisplaySnapshotVisitor(
259            display_blackboard=True, display_activity_stream=True
260        )
261    )
262    behaviour_tree.setup(timeout=15)
263
264    ####################
265    # Tick Tock
266    ####################
267    if args.interactive:
268        py_trees.console.read_single_keypress()
269    while True:
270        try:
271            behaviour_tree.tick()
272            if args.interactive:
273                py_trees.console.read_single_keypress()
274            else:
275                time.sleep(0.5)
276        except KeyboardInterrupt:
277            break
278    print("\n")

py-trees-demo-pick-up-where-you-left-off

A py_trees demo.

A demonstration of the ‘pick up where you left off’ idiom.

A common behaviour tree pattern that allows you to resume work after being interrupted by a high priority interrupt.

EVENTS

  • 2 : task one done, task two running

  • 3 : high priority interrupt

  • 7 : task two restarts

  • 9 : task two done

usage: py-trees-demo-pick-up-where-you-left-off [-h] [-r | -i]

Named Arguments

-r, --render

render dot tree to file

Default: False

-i, --interactive

pause and wait for keypress at each tick

Default: False

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";
}
_images/pick_up_where_you_left_off.gif
py_trees.demos.pick_up_where_you_left_off.command_line_argument_parser() ArgumentParser[source]

Process command line arguments.

Returns

the argument parser

Return type

ArgumentParser

py_trees.demos.pick_up_where_you_left_off.create_root() Behaviour[source]

Create the root behaviour and it’s subtree.

Returns

the root behaviour

Return type

Behaviour

py_trees.demos.pick_up_where_you_left_off.description(root: Behaviour) str[source]

Print description and usage information about the program.

Returns

the program description string

Parameters

root (Behaviour) –

Return type

str

py_trees.demos.pick_up_where_you_left_off.epilog() Optional[str][source]

Print a noodly epilog for –help.

Returns

the noodly message

Return type

Optional[str]

py_trees.demos.pick_up_where_you_left_off.main() None[source]

Entry point for the demo script.

Return type

None

py_trees.demos.pick_up_where_you_left_off.post_tick_handler(snapshot_visitor: SnapshotVisitor, behaviour_tree: BehaviourTree) None[source]

Print an ascii tree with the current snapshot status.

Parameters
Return type

None

py_trees.demos.pick_up_where_you_left_off.pre_tick_handler(behaviour_tree: BehaviourTree) None[source]

Print a banner immediately before every tick of the tree.

Parameters

behaviour_tree (BehaviourTree) – the tree custodian

Return type

None

py_trees/demos/pick_up_where_you_left_off.py
  1#!/usr/bin/env python
  2#
  3# License: BSD
  4#   https://raw.githubusercontent.com/splintered-reality/py_trees/devel/LICENSE
  5#
  6##############################################################################
  7# Documentation
  8##############################################################################
  9
 10"""
 11A py_trees demo.
 12
 13.. argparse::
 14   :module: py_trees.demos.pick_up_where_you_left_off
 15   :func: command_line_argument_parser
 16   :prog: py-trees-demo-pick-up-where-you-left-off
 17
 18.. graphviz:: dot/pick_up_where_you_left_off.dot
 19
 20.. image:: images/pick_up_where_you_left_off.gif
 21"""
 22
 23##############################################################################
 24# Imports
 25##############################################################################
 26
 27import argparse
 28import functools
 29import sys
 30import time
 31import typing
 32
 33import py_trees
 34import py_trees.console as console
 35
 36##############################################################################
 37# Classes
 38##############################################################################
 39
 40
 41def description(root: py_trees.behaviour.Behaviour) -> str:
 42    """
 43    Print description and usage information about the program.
 44
 45    Returns:
 46       the program description string
 47    """
 48    content = "A demonstration of the 'pick up where you left off' idiom.\n\n"
 49    content += "A common behaviour tree pattern that allows you to resume\n"
 50    content += "work after being interrupted by a high priority interrupt.\n"
 51    content += "\n"
 52    content += "EVENTS\n"
 53    content += "\n"
 54    content += " -  2 : task one done, task two running\n"
 55    content += " -  3 : high priority interrupt\n"
 56    content += " -  7 : task two restarts\n"
 57    content += " -  9 : task two done\n"
 58    content += "\n"
 59    if py_trees.console.has_colours:
 60        banner_line = console.green + "*" * 79 + "\n" + console.reset
 61        s = banner_line
 62        s += (
 63            console.bold_white
 64            + "Pick Up Where you Left Off".center(79)
 65            + "\n"
 66            + console.reset
 67        )
 68        s += banner_line
 69        s += "\n"
 70        s += content
 71        s += "\n"
 72        s += py_trees.display.unicode_tree(root)
 73        s += "\n"
 74        s += banner_line
 75    else:
 76        s = content
 77    return s
 78
 79
 80def epilog() -> typing.Optional[str]:
 81    """
 82    Print a noodly epilog for --help.
 83
 84    Returns:
 85       the noodly message
 86    """
 87    if py_trees.console.has_colours:
 88        return (
 89            console.cyan
 90            + "And his noodly appendage reached forth to tickle the blessed...\n"
 91            + console.reset
 92        )
 93    else:
 94        return None
 95
 96
 97def command_line_argument_parser() -> argparse.ArgumentParser:
 98    """
 99    Process command line arguments.
100
101    Returns:
102        the argument parser
103    """
104    parser = argparse.ArgumentParser(
105        description=description(create_root()),
106        epilog=epilog(),
107        formatter_class=argparse.RawDescriptionHelpFormatter,
108    )
109    group = parser.add_mutually_exclusive_group()
110    group.add_argument(
111        "-r", "--render", action="store_true", help="render dot tree to file"
112    )
113    group.add_argument(
114        "-i",
115        "--interactive",
116        action="store_true",
117        help="pause and wait for keypress at each tick",
118    )
119    return parser
120
121
122def pre_tick_handler(behaviour_tree: py_trees.trees.BehaviourTree) -> None:
123    """Print a banner immediately before every tick of the tree.
124
125    Args:
126        behaviour_tree (:class:`~py_trees.trees.BehaviourTree`): the tree custodian
127
128    """
129    print("\n--------- Run %s ---------\n" % behaviour_tree.count)
130
131
132def post_tick_handler(
133    snapshot_visitor: py_trees.visitors.SnapshotVisitor,
134    behaviour_tree: py_trees.trees.BehaviourTree,
135) -> None:
136    """Print an ascii tree with the current snapshot status."""
137    print(
138        "\n"
139        + py_trees.display.unicode_tree(
140            root=behaviour_tree.root,
141            visited=snapshot_visitor.visited,
142            previously_visited=snapshot_visitor.previously_visited,
143        )
144    )
145
146
147def create_root() -> py_trees.behaviour.Behaviour:
148    """
149    Create the root behaviour and it's subtree.
150
151    Returns:
152        the root behaviour
153    """
154    task_one = py_trees.behaviours.StatusQueue(
155        name="Task 1",
156        queue=[
157            py_trees.common.Status.RUNNING,
158            py_trees.common.Status.RUNNING,
159        ],
160        eventually=py_trees.common.Status.SUCCESS,
161    )
162    task_two = py_trees.behaviours.StatusQueue(
163        name="Task 2",
164        queue=[
165            py_trees.common.Status.RUNNING,
166            py_trees.common.Status.RUNNING,
167        ],
168        eventually=py_trees.common.Status.SUCCESS,
169    )
170    high_priority_interrupt = py_trees.decorators.RunningIsFailure(
171        name="Running is Failure",
172        child=py_trees.behaviours.Periodic(name="High Priority", n=3),
173    )
174    piwylo = py_trees.idioms.pick_up_where_you_left_off(
175        name="Pick Up\nWhere You\nLeft Off", tasks=[task_one, task_two]
176    )
177    root = py_trees.composites.Selector(name="Root", memory=False)
178    root.add_children([high_priority_interrupt, piwylo])
179
180    return root
181
182
183##############################################################################
184# Main
185##############################################################################
186
187
188def main() -> None:
189    """Entry point for the demo script."""
190    args = command_line_argument_parser().parse_args()
191    py_trees.logging.level = py_trees.logging.Level.DEBUG
192    root = create_root()
193    print(description(root))
194
195    ####################
196    # Rendering
197    ####################
198    if args.render:
199        py_trees.display.render_dot_tree(root)
200        sys.exit()
201
202    ####################
203    # Tree Stewardship
204    ####################
205    behaviour_tree = py_trees.trees.BehaviourTree(root)
206    behaviour_tree.add_pre_tick_handler(pre_tick_handler)
207    behaviour_tree.visitors.append(py_trees.visitors.DebugVisitor())
208    snapshot_visitor = py_trees.visitors.SnapshotVisitor()
209    behaviour_tree.add_post_tick_handler(
210        functools.partial(post_tick_handler, snapshot_visitor)
211    )
212    behaviour_tree.visitors.append(snapshot_visitor)
213    behaviour_tree.setup(timeout=15)
214
215    ####################
216    # Tick Tock
217    ####################
218    if args.interactive:
219        py_trees.console.read_single_keypress()
220    for _unused_i in range(1, 11):
221        try:
222            behaviour_tree.tick()
223            if args.interactive:
224                py_trees.console.read_single_keypress()
225            else:
226                time.sleep(0.5)
227        except KeyboardInterrupt:
228            break
229    print("\n")