Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
72c3ff6
wip
ivanpauno Dec 10, 2021
d72dc29
wip 2
ivanpauno Dec 13, 2021
fb17060
Fix possible ownership issue
ivanpauno Dec 13, 2021
b0bf636
make basic example work
ivanpauno Dec 14, 2021
19d5234
Make all the services work, checked by doing manual testing
ivanpauno Dec 15, 2021
c01fe6f
More consistent style
ivanpauno Dec 15, 2021
0c3b02b
Add some docs, add ManagedEntity concept
ivanpauno Dec 15, 2021
8efe11c
Make all linters happy
ivanpauno Dec 15, 2021
581d030
Quick lifecycle publisher implementation
ivanpauno Dec 15, 2021
d8451be
Minor style change
ivanpauno Dec 15, 2021
f5967cf
Fix issues introduced when implementing LifecyclePublisher, automatic…
ivanpauno Dec 15, 2021
ed57e05
Fixes to make it work again
ivanpauno Dec 15, 2021
30cb7a0
Modularize the implementation
ivanpauno Dec 16, 2021
b53bb36
Improve SimpleManagedEntity.when_enabled
ivanpauno Dec 16, 2021
d1ff708
Add some docs to lifecycle publisher
ivanpauno Dec 16, 2021
2ac7da1
Register all necessary callbacks, add State abstraction, other minor …
ivanpauno Dec 16, 2021
33157f1
Minor style changes, add local API to trigger transitions
ivanpauno Dec 17, 2021
ebd1d9a
Test LifecycleNode constructor, fix when enable_communication_interfa…
ivanpauno Dec 17, 2021
c6c4d73
test transitions + fixes
ivanpauno Dec 17, 2021
7517641
Add tests for lifecycle publisher
ivanpauno Dec 17, 2021
ce0d256
Please flake8
ivanpauno Dec 17, 2021
bd253ac
Minor style change after writting the demos
ivanpauno Dec 20, 2021
324d4ef
Fix misspelled word
ivanpauno Dec 20, 2021
2616315
Make some attributes in Node "protected" instead of private for easie…
ivanpauno Dec 20, 2021
b57a779
Remove TODO, add better docs
ivanpauno Dec 20, 2021
041ea6d
Remove TODO in __register_callback
ivanpauno Dec 20, 2021
a72645b
Remove TODO
ivanpauno Dec 20, 2021
bc98247
Check transition callbacks return code type. Check type of added mana…
ivanpauno Dec 20, 2021
6c20f80
Check that state is 'unconfigured' also before transition
ivanpauno Dec 22, 2021
928ee79
Test lifecycle services
ivanpauno Dec 22, 2021
59fcc4c
Delete early debugging code
ivanpauno Dec 22, 2021
c31b4f6
Please linters
ivanpauno Dec 22, 2021
4ca2a36
Test the two remaining shutdown transitions. Correct error handling w…
ivanpauno Dec 22, 2021
c693398
Address peer review comments
ivanpauno Dec 23, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions rclpy/rclpy/lifecycle/managed_entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,15 @@ def on_deactivate(self, state) -> TransitionCallbackReturn:
return TransitionCallbackReturn.SUCCESS

@property
def enabled(self):
def is_activated(self):
return self._enabled

@staticmethod
def when_enabled(wrapped=None, *, when_not_enabled=None):
def decorator(wrapped):
@wraps(wrapped)
def only_when_enabled_wrapper(self: SimpleManagedEntity, *args, **kwargs):
if not self.enabled:
if not self._enabled:
if when_not_enabled is not None:
when_not_enabled()
return
Expand Down
139 changes: 93 additions & 46 deletions rclpy/rclpy/lifecycle/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,8 @@ def __init__(
]
for s in lifecycle_services:
callback_group.add_entity(s)
# TODO(ivanpauno): Modify attribute in Node to be "protected" instead of "private".
# i.e. Node.__services -> Node._services
# Maybe the same with similar attributes (__publishers, etc).
# Maybe have some interface to add a service/etc instead (?).
self._Node__services.extend(lifecycle_services)
# Extend base class list of services, so they are added to the executor when spinning.
self._services.extend(lifecycle_services)

def trigger_configure(self):
return self.__change_state(lifecycle_msgs.msg.Transition.TRANSITION_CONFIGURE)
Expand All @@ -166,6 +163,7 @@ def trigger_shutdown(self):
lifecycle_msgs.msg.Transition.TRANSITION_INACTIVE_SHUTDOWN)
if current_state == 'active':
return self.__change_state(lifecycle_msgs.msg.Transition.TRANSITION_ACTIVE_SHUTDOWN)
raise _rclpy.RCLError('Shutdown transtion not possible')

def trigger_activate(self):
return self.__change_state(lifecycle_msgs.msg.Transition.TRANSITION_ACTIVATE)
Expand All @@ -174,52 +172,106 @@ def trigger_deactivate(self):
return self.__change_state(lifecycle_msgs.msg.Transition.TRANSITION_DEACTIVATE)

def add_managed_entity(self, entity: ManagedEntity):
if not isinstance(entity, ManagedEntity):
raise TypeError('Expected a rclpy.lifecycle.ManagedEntity instance.')
self._managed_entities.add(entity)

def on_configure(self, state) -> TransitionCallbackReturn:
def __transition_callback_impl(self, callback_name: str, state: LifecycleState):
for entity in self._managed_entities:
ret = entity.on_configure(state)
# TODO(ivanpauno): Should we stop calling the other managed entities callabacks
# if one fails or errors?
# Should the behavior be the same in all the other cases?
cb = getattr(entity, callback_name)
ret = cb(state)
if not isinstance(ret, TransitionCallbackReturn):
raise TypeError(
f'{callback_name}() return value of class {type(entity)} should be'
' `TransitionCallbackReturn`.\n'
f'Instance of the class that caused the failure: {entity}')
if ret != TransitionCallbackReturn.SUCCESS:
return ret
return TransitionCallbackReturn.SUCCESS

def on_cleanup(self, state) -> TransitionCallbackReturn:
for entity in self._managed_entities:
ret = entity.on_cleanup(state)
if ret != TransitionCallbackReturn.SUCCESS:
return ret
return TransitionCallbackReturn.SUCCESS
def on_configure(self, state: LifecycleState) -> TransitionCallbackReturn:
"""
Handle a configuring transition.

def on_shutdown(self, state) -> TransitionCallbackReturn:
for entity in self._managed_entities:
ret = entity.on_shutdown(state)
if ret != TransitionCallbackReturn.SUCCESS:
return ret
return TransitionCallbackReturn.SUCCESS
This is the default on_configure() callback.
It will call all on_configure() callbacks of managed entities, giving up at the first
entity that returns TransitionCallbackReturn.FAILURE or TransitionCallbackReturn.ERROR.

def on_activate(self, state) -> TransitionCallbackReturn:
for entity in self._managed_entities:
ret = entity.on_activate(state)
if ret != TransitionCallbackReturn.SUCCESS:
return ret
return TransitionCallbackReturn.SUCCESS
It's possible to override this callback if the default behavior is not desired.
If you only want to extend what this callback does, make sure to call
super().on_configure() in derived classes.
"""
return self.__transition_callback_impl('on_configure', state)

def on_deactivate(self, state) -> TransitionCallbackReturn:
for entity in self._managed_entities:
ret = entity.on_deactivate(state)
if ret != TransitionCallbackReturn.SUCCESS:
return ret
return TransitionCallbackReturn.SUCCESS
def on_cleanup(self, state: LifecycleState) -> TransitionCallbackReturn:
"""
Handle a cleaning up transition.

def on_error(self, state) -> TransitionCallbackReturn:
for entity in self._managed_entities:
ret = entity.on_error(state)
if ret != TransitionCallbackReturn.SUCCESS:
return ret
return TransitionCallbackReturn.SUCCESS
This is the default on_cleanup() callback.
It will call all on_cleanup() callbacks of managed entities, giving up at the first
entity that returns TransitionCallbackReturn.FAILURE or TransitionCallbackReturn.ERROR.

It's possible to override this callback if the default behavior is not desired.
If you only want to extend what this callback does, make sure to call
super().on_cleanup() in derived classes.
"""
return self.__transition_callback_impl('on_cleanup', state)

def on_shutdown(self, state: LifecycleState) -> TransitionCallbackReturn:
"""
Handle a shutting down transition.

This is the default on_shutdown() callback.
It will call all on_shutdown() callbacks of managed entities, giving up at the first
entity that returns TransitionCallbackReturn.FAILURE or TransitionCallbackReturn.ERROR.

It's possible to override this callback if the default behavior is not desired.
If you only want to extend what this callback does, make sure to call
super().on_shutdown() in derived classes.
"""
return self.__transition_callback_impl('on_shutdown', state)

def on_activate(self, state: LifecycleState) -> TransitionCallbackReturn:
"""
Handle an activating transition.

This is the default on_activate() callback.
It will call all on_activate() callbacks of managed entities, giving up at the first
entity that returns TransitionCallbackReturn.FAILURE or TransitionCallbackReturn.ERROR.

It's possible to override this callback if the default behavior is not desired.
If you only want to extend what this callback does, make sure to call
super().on_activate() in derived classes.
"""
return self.__transition_callback_impl('on_activate', state)

def on_deactivate(self, state: LifecycleState) -> TransitionCallbackReturn:
"""
Handle a deactivating transition.

This is the default on_deactivate() callback.
It will call all on_deactivate() callbacks of managed entities, giving up at the first
entity that returns TransitionCallbackReturn.FAILURE or TransitionCallbackReturn.ERROR.

It's possible to override this callback if the default behavior is not desired.
If you only want to extend what this callback does, make sure to call
super().on_deactivate() in derived classes.
"""
return self.__transition_callback_impl('on_deactivate', state)

def on_error(self, state: LifecycleState) -> TransitionCallbackReturn:
"""
Handle a transition error.

This is the default on_error() callback.
It will call all on_error() callbacks of managed entities, giving up at the first
entity that returns TransitionCallbackReturn.FAILURE or TransitionCallbackReturn.ERROR.

It's possible to override this callback if the default behavior is not desired.
If you only want to extend what this callback does, make sure to call
super().on_error() in derived classes.
"""
return self.__transition_callback_impl('on_error', state)

def create_lifecycle_publisher(self, *args, **kwargs):
# TODO(ivanpauno): Should we override lifecycle publisher?
Expand Down Expand Up @@ -252,9 +304,6 @@ def __register_callback(
a TransitionCallbackReturn value.
"""
self._callbacks[state_id] = callback
# TODO(ivanpauno): Copying rclcpp style.
# Maybe having a return value doesn't make sense (?).
# Should we error/warn if overridding an existing callback?
return True

def __execute_callback(
Expand Down Expand Up @@ -289,8 +338,6 @@ def __change_state(self, transition_id: int) -> TransitionCallbackReturn:
return cb_return_code

def __check_is_initialized(self):
# TODO(ivanpauno): This sanity check is probably not needed, just doing the same checks as
# rclcpp for the moment.
if not self._state_machine.initialized:
raise RuntimeError(
'Internal error: got service request while lifecycle state machine '
Expand Down Expand Up @@ -376,7 +423,7 @@ class LifecycleNode(LifecycleNodeMixin, Node):
A ROS 2 managed node.

This class extends Node with the methods provided by LifecycleNodeMixin.
Methods in LifecycleNodeMixin overridde the ones in Node.
Methods in LifecycleNodeMixin override the ones in Node.
"""

def __init__(self, node_name, *, enable_communication_interface: bool = True, **kwargs):
Expand Down
Loading