py_trees 2.5.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. py_trees/__init__.py +39 -0
  2. py_trees/behaviour.py +428 -0
  3. py_trees/behaviours.py +808 -0
  4. py_trees/blackboard.py +1397 -0
  5. py_trees/common.py +302 -0
  6. py_trees/composites.py +778 -0
  7. py_trees/console.py +432 -0
  8. py_trees/decorators.py +936 -0
  9. py_trees/demos/README.md +8 -0
  10. py_trees/demos/__init__.py +31 -0
  11. py_trees/demos/action.py +200 -0
  12. py_trees/demos/blackboard.py +278 -0
  13. py_trees/demos/blackboard_namespaces.py +146 -0
  14. py_trees/demos/blackboard_remappings.py +141 -0
  15. py_trees/demos/context_switching.py +194 -0
  16. py_trees/demos/display_modes.py +151 -0
  17. py_trees/demos/dot_graphs.py +160 -0
  18. py_trees/demos/either_or.py +254 -0
  19. py_trees/demos/eternal_guard.py +214 -0
  20. py_trees/demos/lifecycle.py +149 -0
  21. py_trees/demos/logging.py +234 -0
  22. py_trees/demos/pick_up_where_you_left_off.py +213 -0
  23. py_trees/demos/ports/__init__.py +9 -0
  24. py_trees/demos/ports/basic.py +89 -0
  25. py_trees/demos/ports/nested_subtrees.py +120 -0
  26. py_trees/demos/ports/nested_subtrees.xml +65 -0
  27. py_trees/demos/ports/remapping.py +165 -0
  28. py_trees/demos/ports/xml_tree.py +113 -0
  29. py_trees/demos/ports/xml_tree.xml +15 -0
  30. py_trees/demos/selector.py +149 -0
  31. py_trees/demos/sequence.py +147 -0
  32. py_trees/demos/stewardship.py +261 -0
  33. py_trees/display.py +1075 -0
  34. py_trees/idioms.py +259 -0
  35. py_trees/logging.py +122 -0
  36. py_trees/meta.py +74 -0
  37. py_trees/parsers/__init__.py +21 -0
  38. py_trees/parsers/behaviour_tree_xml.py +1066 -0
  39. py_trees/ports.py +808 -0
  40. py_trees/ports_utils.py +464 -0
  41. py_trees/programs/__init__.py +15 -0
  42. py_trees/programs/render.py +205 -0
  43. py_trees/py.typed +0 -0
  44. py_trees/syntax_highlighting.py +73 -0
  45. py_trees/tests.py +121 -0
  46. py_trees/timers.py +87 -0
  47. py_trees/trees.py +481 -0
  48. py_trees/utilities.py +196 -0
  49. py_trees/version.py +17 -0
  50. py_trees/visitors.py +226 -0
  51. py_trees-2.5.0.dist-info/METADATA +186 -0
  52. py_trees-2.5.0.dist-info/RECORD +55 -0
  53. py_trees-2.5.0.dist-info/WHEEL +5 -0
  54. py_trees-2.5.0.dist-info/entry_points.txt +21 -0
  55. py_trees-2.5.0.dist-info/licenses/LICENSE +32 -0
py_trees/__init__.py ADDED
@@ -0,0 +1,39 @@
1
+ #
2
+ # License: BSD
3
+ # https://raw.githubusercontent.com/splintered-reality/py_trees/devel/LICENSE
4
+ #
5
+ ##############################################################################
6
+ # Documentation
7
+ ##############################################################################
8
+
9
+ """This is the top-level namespace of the py_trees package."""
10
+
11
+ ##############################################################################
12
+ # Imports
13
+ ##############################################################################
14
+
15
+ # Preserve import order: demos & programs depend on the modules above them.
16
+ # isort: off
17
+ from . import behaviour
18
+ from . import behaviours
19
+ from . import blackboard
20
+ from . import common
21
+ from . import composites
22
+ from . import console
23
+ from . import decorators
24
+ from . import display
25
+ from . import idioms
26
+ from . import logging
27
+ from . import meta
28
+ from . import ports
29
+ from . import syntax_highlighting
30
+ from . import tests
31
+ from . import timers
32
+ from . import trees
33
+ from . import utilities
34
+ from . import version
35
+ from . import visitors
36
+
37
+ from . import demos
38
+ from . import programs
39
+ # isort: on
py_trees/behaviour.py ADDED
@@ -0,0 +1,428 @@
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
+ """The core behaviour template for all py_tree behaviours."""
11
+
12
+ ##############################################################################
13
+ # Imports
14
+ ##############################################################################
15
+
16
+ from __future__ import annotations
17
+
18
+ import abc
19
+ import re
20
+ import typing
21
+ import uuid
22
+
23
+ from . import blackboard, common, logging
24
+
25
+ ##############################################################################
26
+ # Behaviour BluePrint
27
+ ##############################################################################
28
+
29
+
30
+ class Behaviour(abc.ABC):
31
+ """A parent class for all user definable tree behaviours.
32
+
33
+ Args:
34
+ name: the behaviour name, defaults to auto-generating from the class name
35
+
36
+ Raises:
37
+ TypeError: if the provided name is not a string
38
+
39
+ Attributes:
40
+ ~py_trees.behaviours.Behaviour.id (:class:`uuid.UUID`): automagically generated unique identifier
41
+ for the behaviour
42
+ ~py_trees.behaviours.Behaviour.name (:obj:`str`): the behaviour name
43
+ ~py_trees.behaviours.Behaviour.blackboards (typing.List[py_trees.blackboard.Client]): collection of attached
44
+ blackboard clients
45
+ ~py_trees.behaviours.Behaviour.status (:class:`~py_trees.common.Status`): the behaviour status
46
+ (:data:`~py_trees.common.Status.INVALID`,
47
+ :data:`~py_trees.common.Status.RUNNING`,
48
+ :data:`~py_trees.common.Status.FAILURE`,
49
+ :data:`~py_trees.common.Status.SUCCESS`)
50
+ ~py_trees.behaviours.Behaviour.parent (:class:`~py_trees.behaviour.Behaviour`): a
51
+ :class:`~py_trees.composites.Composite` instance if nested in a tree, otherwise None
52
+ ~py_trees.behaviours.Behaviour.children ([:class:`~py_trees.behaviour.Behaviour`]): empty for regular
53
+ behaviours, populated for composites
54
+ ~py_trees.behaviours.Behaviour.logger (:class:`logging.Logger`): a simple logging mechanism
55
+ ~py_trees.behaviours.Behaviour.feedback_message(:obj:`str`): improve debugging with a simple message
56
+ ~py_trees.behaviours.Behaviour.blackbox_level (:class:`~py_trees.common.BlackBoxLevel`): a helper variable
57
+ for dot graphs and runtime gui's to collapse/explode entire subtrees dependent upon the blackbox level.
58
+
59
+ .. seealso::
60
+ * :ref:`Skeleton Behaviour Template <skeleton-behaviour-include>`
61
+ * :ref:`The Lifecycle Demo <py-trees-demo-behaviour-lifecycle-program>`
62
+ * :ref:`The Action Behaviour Demo <py-trees-demo-action-behaviour-program>`
63
+ """
64
+
65
+ def __init__(self, name: str):
66
+ if not isinstance(name, str):
67
+ raise TypeError(f"a behaviour name should be a string, but you passed in {type(name)}")
68
+ self.id = uuid.uuid4() # used to uniquely identify this node (helps with removing children from a tree)
69
+ self.name: str = name
70
+ self.blackboards: list[blackboard.Client] = []
71
+ self.qualified_name = f"{self.__class__.__qualname__}/{self.name}" # convenience
72
+ self.status = common.Status.INVALID
73
+ self.iterator = self.tick()
74
+ self.parent: Behaviour | None = None # will get set if a behaviour is added to a composite
75
+ self.children: list[Behaviour] = [] # only set by composite behaviours
76
+ self.logger = logging.Logger(name)
77
+ self.feedback_message = "" # useful for debugging, or human readable updates, but not necessary to implement
78
+ self.blackbox_level = common.BlackBoxLevel.NOT_A_BLACKBOX
79
+
80
+ ############################################
81
+ # User Customisable Callbacks
82
+ ############################################
83
+
84
+ def setup(self, **kwargs: typing.Any) -> None: # noqa: B027
85
+ """
86
+ Set up and verify infrastructure (middleware connections, etc) is available.
87
+
88
+ Users should override this method for any configuration and/or validation
89
+ that is necessary prior to ticking the tree. Such construction is best
90
+ done here rather than in __init__ since there is no guarantee at __init__
91
+ that the infrastructure is ready or even available (e.g. you may be just
92
+ rendering dot graphs of the trees, no robot around).
93
+
94
+ Examples:
95
+ * establishing a middleware connection to a sensor or driver
96
+ * ensuring a sensor or driver is in a 'ready' state
97
+
98
+ This method will typically be called before a tree's first tick as this gives
99
+ the application time to check and verify that everything is in a ready state before
100
+ executing. This is especially important given that a tree does not always tick
101
+ every behaviour and if not checked up-front, it may be some time before
102
+ discovering a behaviour was in a broken state.
103
+
104
+ .. tip::
105
+ When to use :meth:`~py_trees.behaviour.Behaviour.__init__`,
106
+ :meth:`~py_trees.behaviour.Behaviour.setup` and when to use
107
+ :meth:`~py_trees.behaviour.Behaviour.initialise`?
108
+
109
+ Use :meth:`~py_trees.behaviour.Behaviour.__init__` for configuration of
110
+ non-runtime dependencies (e.g. no middleware).
111
+
112
+ Use :meth:`~py_trees.behaviour.Behaviour.setup` for one-offs or to get
113
+ early signal that everything (e.g. middleware) is ready to go.
114
+
115
+ Use :meth:`~py_trees.behaviour.Behaviour.initialise` for just-in-time
116
+ configurations and/or checks.
117
+
118
+ There are times when it makes sense to do all three. For example,
119
+ pythonic variable configuration in :meth:`~py_trees.behaviour.Behaviour.__init__`,
120
+ middleware service client creation / server existence checks in
121
+ :meth:`~py_trees.behaviour.Behaviour.setup` and a just-in-time check
122
+ to ensure the server is still available in :meth:`~py_trees.behaviour.Behaviour.initialise`.
123
+
124
+ .. tip::
125
+
126
+ Faults are notified to the user of the behaviour via exceptions.
127
+ Choice of exception to use is left to the user.
128
+
129
+ .. warning::
130
+
131
+ The kwargs argument is for distributing objects at runtime to behaviours
132
+ before ticking. For example, a simulator instance with which behaviours can
133
+ interact with the simulator's python api, a ros2 node for setting up
134
+ communications. Use sparingly, as this is not proof against keyword conflicts
135
+ amongst disparate libraries of behaviours.
136
+
137
+ Args:
138
+ **kwargs: distribute arguments to this
139
+ behaviour and in turn, all of its children
140
+
141
+ Raises:
142
+ Exception: if this behaviour has a fault in construction or configuration
143
+
144
+ .. seealso:: :meth:`py_trees.behaviour.Behaviour.shutdown`
145
+ """
146
+ pass
147
+
148
+ def initialise(self) -> None: # noqa: B027
149
+ """
150
+ Execute user specified instructions prior to commencement of a new round of activity.
151
+
152
+ Users should override this method to perform any necessary initialising/clearing/resetting
153
+ of variables prior to a new round of activity for the behaviour.
154
+
155
+ This method is automatically called via the :meth:`py_trees.behaviour.Behaviour.tick` method
156
+ whenever the behaviour is not :data:`~py_trees.common.Status.RUNNING`.
157
+
158
+ ... note:: This method can be called more than once in the lifetime of a tree!
159
+ """
160
+ pass
161
+
162
+ def terminate(self, new_status: common.Status) -> None: # noqa: B027
163
+ """
164
+ Execute user specified instructions when the behaviour is stopped.
165
+
166
+ Users should override this method to clean up.
167
+ It will be triggered when a behaviour either
168
+ finishes execution (switching from :data:`~py_trees.common.Status.RUNNING`
169
+ to :data:`~py_trees.common.Status.FAILURE` || :data:`~py_trees.common.Status.SUCCESS`)
170
+ or it got interrupted by a higher priority branch (switching to
171
+ :data:`~py_trees.common.Status.INVALID`). Remember that
172
+ the :meth:`~py_trees.behaviour.Behaviour.initialise` method
173
+ will handle resetting of variables before re-entry, so this method is about
174
+ disabling resources until this behaviour's next tick. This could be a indeterminably
175
+ long time. e.g.
176
+
177
+ * cancel an external action that got started
178
+ * shut down any temporary communication handles
179
+
180
+ Args:
181
+ new_status (:class:`~py_trees.common.Status`): the behaviour is transitioning to this new status
182
+
183
+ .. warning:: Do not set `self.status = new_status` here, that is automatically handled
184
+ by the :meth:`~py_trees.behaviour.Behaviour.stop` method.
185
+ Use the argument purely for introspection purposes (e.g.
186
+ comparing the current state in `self.status` with the state it will transition to in
187
+ `new_status`.
188
+
189
+ .. seealso:: :meth:`py_trees.behaviour.Behaviour.stop`
190
+ """
191
+ pass
192
+
193
+ @abc.abstractmethod
194
+ def update(self) -> common.Status:
195
+ """
196
+ Execute user specified instructions when the behaviour is ticked.
197
+
198
+ Users should override this method to perform any logic required to
199
+ arrive at a decision on the behaviour's new status. It is the primary worker function called
200
+ by the :meth:`~py_trees.behaviour.Behaviour.tick` mechanism.
201
+
202
+ Returns:
203
+ the behaviour's new status :class:`~py_trees.common.Status`
204
+
205
+ .. tip:: This method should be almost instantaneous and non-blocking
206
+
207
+ .. seealso:: :meth:`py_trees.behaviour.Behaviour.tick`
208
+ """
209
+ return common.Status.INVALID
210
+
211
+ def shutdown(self) -> None: # noqa: B027
212
+ """
213
+ Destroy setup infrastructure (the antithesis of setup).
214
+
215
+ Users should override this method for any custom destruction of infrastructure
216
+ usually brought into being in :meth:`~py_trees.behaviour.Behaviour.setup`.
217
+
218
+ Raises:
219
+ Exception: of whatever flavour the child raises when errors occur on destruction
220
+
221
+ .. seealso:: :meth:`py_trees.behaviour.Behaviour.setup`
222
+ """
223
+ pass
224
+
225
+ ############################################
226
+ # Private Methods - use inside a behaviour
227
+ ############################################
228
+
229
+ def attach_blackboard_client(self, name: str | None = None, namespace: str | None = None) -> blackboard.Client:
230
+ """
231
+ Create and attach a blackboard to this behaviour.
232
+
233
+ Args:
234
+ name: human-readable (not necessarily unique) name for the client
235
+ namespace: sandbox the client to variables behind this namespace
236
+
237
+ Returns:
238
+ a handle to the attached blackboard client
239
+ """
240
+ if name is None:
241
+ count = len(self.blackboards)
242
+ name = self.name if (count == 0) else self.name + f"-{count}"
243
+ new_blackboard = blackboard.Client(name=name, namespace=namespace)
244
+ self.blackboards.append(new_blackboard)
245
+ return new_blackboard
246
+
247
+ ############################################
248
+ # Public - lifecycle API
249
+ ############################################
250
+
251
+ def setup_with_descendants(self) -> None:
252
+ """Call setup on this child, its children (its children's children, )."""
253
+ for child in self.children:
254
+ for node in child.iterate():
255
+ node.setup()
256
+ self.setup()
257
+
258
+ def tick_once(self) -> None:
259
+ """Tick the object without iterating step-by-step over the children (i.e. without generators)."""
260
+ # no logger necessary here...it directly relays to tick
261
+ for _unused in self.tick():
262
+ pass
263
+
264
+ def tick(self) -> typing.Iterator[Behaviour]:
265
+ """
266
+ Tick the behaviour.
267
+
268
+ This function is a generator that can be used by an iterator on
269
+ an entire behaviour tree. It handles the logic for deciding when to
270
+ call the user's :meth:`~py_trees.behaviour.Behaviour.initialise`
271
+ and :meth:`~py_trees.behaviour.Behaviour.terminate` methods as well as making the
272
+ actual call to the user's :meth:`~py_trees.behaviour.Behaviour.update` method that determines the
273
+ behaviour's new status once the tick has finished. Once done, it will
274
+ then yield itself (generator mechanism) so that it can be used as part of
275
+ an iterator for the entire tree.
276
+
277
+ .. code-block:: python
278
+
279
+ for node in my_behaviour.tick():
280
+ print("Do something")
281
+
282
+ .. note::
283
+
284
+ This is a generator function, you must use this with *yield*. If you need a direct call,
285
+ prefer :meth:`~py_trees.behaviour.Behaviour.tick_once` instead.
286
+
287
+ Yields:
288
+ a reference to itself
289
+
290
+ .. warning::
291
+ Users should not override this method to provide custom tick behaviour. The
292
+ :meth:`~py_trees.behaviour.Behaviour.update` method has been provided for that purpose.
293
+ """
294
+ self.logger.debug(f"{self.__class__.__name__}.tick()")
295
+ if self.status != common.Status.RUNNING:
296
+ self.initialise()
297
+ # don't set self.status yet, terminate() may need to check what the current state is first
298
+ new_status = self.update()
299
+ if new_status not in list(common.Status):
300
+ self.logger.error(f"A behaviour returned an invalid status, setting to INVALID [{new_status}][{self.name}]")
301
+ new_status = common.Status.INVALID
302
+ if new_status != common.Status.RUNNING:
303
+ self.stop(new_status)
304
+ self.status = new_status
305
+ yield self
306
+
307
+ def iterate(self, direct_descendants: bool = False) -> typing.Iterator[Behaviour]:
308
+ """
309
+ Iterate over this child and its children.
310
+
311
+ This utilises python generators for looping. To traverse the entire tree:
312
+
313
+ .. code-block:: python
314
+
315
+ for node in my_behaviour.iterate():
316
+ print("Name: {0}".format(node.name))
317
+
318
+ Args:
319
+ direct_descendants (:obj:`bool`): only yield children one step away from this behaviour.
320
+
321
+ Yields:
322
+ :class:`~py_trees.behaviour.Behaviour`: one of its children
323
+ """
324
+ for child in self.children:
325
+ if not direct_descendants:
326
+ yield from child.iterate()
327
+ else:
328
+ yield child
329
+ yield self
330
+
331
+ # TODO: better type refinement of 'viso=itor'
332
+ def visit(self, visitor: typing.Any) -> None:
333
+ """
334
+ Introspect on this behaviour with a visitor.
335
+
336
+ This is functionality that enables external introspection into the behaviour. It gets used
337
+ by the tree manager classes to collect information as ticking traverses a tree.
338
+
339
+ Args:
340
+ visitor: the visiting class, must have a run(:class:`~py_trees.behaviour.Behaviour`) method.
341
+ """
342
+ visitor.run(self)
343
+
344
+ def stop(self, new_status: common.Status) -> None:
345
+ """
346
+ Stop the behaviour with the specified status.
347
+
348
+ Args:
349
+ new_status: the behaviour is transitioning to this new status
350
+
351
+ This is called to bring the current round of activity for the behaviour to completion, typically
352
+ resulting in a final status of :data:`~py_trees.common.Status.SUCCESS`,
353
+ :data:`~py_trees.common.Status.FAILURE` or :data:`~py_trees.common.Status.INVALID`.
354
+
355
+ .. warning::
356
+ Users should not override this method to provide custom termination behaviour. The
357
+ :meth:`~py_trees.behaviour.Behaviour.terminate` method has been provided for that purpose.
358
+ """
359
+ self.logger.debug(
360
+ "{}.stop({})".format(
361
+ self.__class__.__name__,
362
+ (f"{self.status}->{new_status}" if self.status != new_status else f"{new_status}"),
363
+ )
364
+ )
365
+ self.terminate(new_status)
366
+ self.status = new_status
367
+ self.iterator = self.tick()
368
+
369
+ ############################################
370
+ # Public - introspection API
371
+ ############################################
372
+ def has_parent_with_name(self, name: str) -> bool:
373
+ """
374
+ Search this behaviour's ancestors for one with the specified name.
375
+
376
+ Args:
377
+ name: name of the parent to match, can be a regular expression
378
+
379
+ Returns:
380
+ whether a parent was found or not
381
+ """
382
+ pattern = re.compile(name)
383
+ b = self
384
+ while b.parent is not None:
385
+ if pattern.match(b.parent.name) is not None:
386
+ return True
387
+ b = b.parent
388
+ return False
389
+
390
+ def has_parent_with_instance_type(self, instance_type: type[Behaviour]) -> bool:
391
+ """
392
+ Search this behaviour's ancestors for one of the specified type.
393
+
394
+ Args:
395
+ instance type of the parent to match
396
+
397
+ Returns:
398
+ whether a parent was found or not
399
+ """
400
+ b = self
401
+ while b.parent is not None:
402
+ if isinstance(b.parent, instance_type):
403
+ return True
404
+ b = b.parent
405
+ return False
406
+
407
+ def tip(self) -> Behaviour | None:
408
+ """
409
+ Get the *tip* of this behaviour's subtree (if it has one).
410
+
411
+ This corresponds to the the deepest node that was running before the
412
+ subtree traversal reversed direction and headed back to this node.
413
+
414
+ Returns:
415
+ The deepest node (behaviour) that was running before subtree traversal
416
+ reversed direction, or None if this behaviour's status is
417
+ :data:`~py_trees.common.Status.INVALID`.
418
+ """
419
+ return self if self.status != common.Status.INVALID else None
420
+
421
+
422
+ ##############################################################################
423
+ # Mypy Convenience Types
424
+ ##############################################################################
425
+
426
+
427
+ BehaviourSubClass = typing.TypeVar("BehaviourSubClass", bound=Behaviour)
428
+ # BehaviourUpdateMethod = typing.Callable[[BehaviourSubClass], common.Status]