-
Notifications
You must be signed in to change notification settings - Fork 137
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ROS specific launch #75
Changes from 16 commits
5f843e0
e831e8c
89910e3
dac2580
431853f
691bfd8
313bd70
46bd3e1
03e9d07
5607365
cd0ebe2
42a018e
b4b83da
26ed3b8
a19f2e0
57ada70
15ac0c3
b29224d
838242e
65c2b53
6955bc8
e8abec6
8307696
baefc07
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
# Copyright 2018 Open Source Robotics Foundation, Inc. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
"""Launch a talker and a listener.""" | ||
|
||
import os | ||
import sys | ||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) # noqa | ||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'launch')) # noqa | ||
|
||
import launch | ||
import launch.actions | ||
import launch.events | ||
|
||
from launch_ros import get_default_launch_description | ||
import launch_ros.actions | ||
import launch_ros.events | ||
import launch_ros.events.lifecycle | ||
|
||
import lifecycle_msgs.msg | ||
|
||
|
||
def main(argv=sys.argv[1:]): | ||
"""Main.""" | ||
def _on_transition_event(context): | ||
print('got transition_event {}'.format(context.locals.event)) | ||
|
||
ld = launch.LaunchDescription() | ||
|
||
# Prepare the talker node. | ||
talker_node = launch_ros.actions.LifecycleNode( | ||
node_name='talker', | ||
package='lifecycle', node_executable='lifecycle_talker', output='screen') | ||
|
||
# When the talker reaches the 'inactive' state, make it take the 'activate' transition. | ||
event_handler_for_talker_reaching_inactive_state = launch.actions.RegisterEventHandler( | ||
launch_ros.event_handlers.OnStateTransition( | ||
target_lifecycle_node=talker_node, goal_state='inactive', | ||
entities=[ | ||
launch.actions.LogInfo( | ||
msg="node 'talker' reached the 'inactive' state, 'activating'."), | ||
launch.actions.EmitEvent(event=launch_ros.events.lifecycle.ChangeState( | ||
lifecycle_node_matcher=launch.events.process.matches_action(talker_node), | ||
transition_id=lifecycle_msgs.msg.Transition.TRANSITION_ACTIVATE, | ||
)), | ||
], | ||
) | ||
) | ||
|
||
# When the talker node reaches the 'active' state, log a message and start the listener node. | ||
event_handler_for_talker_reaching_active_state = launch.actions.RegisterEventHandler( | ||
launch_ros.event_handlers.OnStateTransition( | ||
target_lifecycle_node=talker_node, goal_state='active', | ||
entities=[ | ||
launch.actions.LogInfo( | ||
msg="node 'talker' reached the 'active' state, launching 'listener'."), | ||
launch_ros.actions.LifecycleNode( | ||
node_name='listener', | ||
package='lifecycle', node_executable='lifecycle_listener', output='screen'), | ||
], | ||
) | ||
) | ||
|
||
# Make the talker node take the 'configure' transition. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for readability of this file I recommend reversing the creation of these actions so we read about them in the order that they would occur There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The event handler must be registered before sending the event. Do you have a suggestion for how to reorder it that works? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure if we are talking about the same scale. What I meant was add the action that creates the node; add the action that configures the node; add the action that activates the node; add the action that starts the listener. But I think it wasn't clear so you thought I was referring to this individual add_action call. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Checkout 46bd3e1 |
||
emit_event_to_request_that_talker_does_configure_transition = launch.actions.EmitEvent( | ||
event=launch_ros.events.lifecycle.ChangeState( | ||
lifecycle_node_matcher=launch.events.process.matches_action(talker_node), | ||
transition_id=lifecycle_msgs.msg.Transition.TRANSITION_CONFIGURE, | ||
) | ||
) | ||
|
||
# Add the actions to the launch description all at once, because the order matters: | ||
ld.add_action(event_handler_for_talker_reaching_inactive_state) | ||
ld.add_action(event_handler_for_talker_reaching_active_state) | ||
ld.add_action(talker_node) | ||
ld.add_action(emit_event_to_request_that_talker_does_configure_transition) | ||
|
||
print('Starting introspection of launch description...') | ||
print('') | ||
|
||
print(launch.LaunchIntrospector().format_launch_description(ld)) | ||
|
||
print('') | ||
print('Starting launch of launch description...') | ||
print('') | ||
|
||
# ls = LaunchService(argv=argv, debug=True) | ||
ls = launch.LaunchService(argv=argv) | ||
ls.include_launch_description(get_default_launch_description(prefix_output_with_name=False)) | ||
ls.include_launch_description(ld) | ||
ls.run() | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
# Copyright 2018 Open Source Robotics Foundation, Inc. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
"""Launch a talker and a listener.""" | ||
|
||
import os | ||
import sys | ||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) # noqa | ||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'launch')) # noqa | ||
|
||
from launch import LaunchDescription | ||
from launch import LaunchIntrospector | ||
from launch import LaunchService | ||
|
||
from launch_ros import get_default_launch_description | ||
import launch_ros.actions | ||
|
||
|
||
def main(argv=sys.argv[1:]): | ||
"""Main.""" | ||
ld = LaunchDescription([ | ||
launch_ros.actions.Node( | ||
package='demo_nodes_cpp', node_executable='talker', output='screen', | ||
remappings=[('chatter', 'my_chatter')]), | ||
launch_ros.actions.Node( | ||
package='demo_nodes_cpp', node_executable='listener', output='screen', | ||
remappings=[('chatter', 'my_chatter')]), | ||
]) | ||
|
||
print('Starting introspection of launch description...') | ||
print('') | ||
|
||
print(LaunchIntrospector().format_launch_description(ld)) | ||
|
||
print('') | ||
print('Starting launch of launch description...') | ||
print('') | ||
|
||
# ls = LaunchService(debug=True) | ||
ls = LaunchService() | ||
ls.include_launch_description(get_default_launch_description(prefix_output_with_name=False)) | ||
ls.include_launch_description(ld) | ||
ls.run() | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
# Copyright 2018 Open Source Robotics Foundation, Inc. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
"""Main entry point for the `launch_ros` package.""" | ||
|
||
from . import actions | ||
from . import event_handlers | ||
from . import events | ||
from . import substitutions | ||
from .default_launch_description import get_default_launch_description | ||
|
||
__all__ = [ | ||
'actions', | ||
'event_handlers', | ||
'events', | ||
'substitutions', | ||
'get_default_launch_description', | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# Copyright 2018 Open Source Robotics Foundation, Inc. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
"""actions Module.""" | ||
|
||
from .lifecycle_node import LifecycleNode | ||
from .node import Node | ||
|
||
__all__ = [ | ||
'LifecycleNode', | ||
'Node', | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
# Copyright 2018 Open Source Robotics Foundation, Inc. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
"""Module for the LifecycleNode action.""" | ||
|
||
import functools | ||
import logging | ||
from typing import cast | ||
from typing import List | ||
from typing import Optional | ||
from typing import Text | ||
|
||
import launch | ||
from launch.action import Action | ||
|
||
import lifecycle_msgs.msg | ||
import lifecycle_msgs.srv | ||
|
||
from .node import Node | ||
from ..events.lifecycle import ChangeState | ||
from ..events.lifecycle import StateTransition | ||
|
||
_logger = logging.getLogger(name='launch_ros') | ||
|
||
|
||
class LifecycleNode(Node): | ||
"""Action that executes a ROS lifecycle node.""" | ||
|
||
def __init__(self, *, node_name: Text, **kwargs) -> None: | ||
""" | ||
Construct a LifecycleNode action. | ||
|
||
Almost all of the arguments are passed to :class:`Node` and eventually | ||
to :class:`launch.actions.ExecuteProcess`, so see the documentation of | ||
those classes for additional details. | ||
|
||
This action additionally emits some event(s) in certain circumstances: | ||
|
||
- :class:`launch.events.lifecycle.StateTransition`: | ||
|
||
- this event is emitted when a message is published to the | ||
"/<node_name>/transition_event" topic, indicating the lifecycle | ||
node represented by this action changed state | ||
|
||
This action also handles some events related to lifecycle: | ||
|
||
- :class:`launch.events.lifecycle.ChangeState` | ||
|
||
- this event can be targeted to a single lifecycle node, or more than | ||
one, or even all lifecycle nodes, and it requests the targeted nodes | ||
to change state, see its documentation for more details. | ||
""" | ||
super().__init__(node_name=node_name, **kwargs) | ||
self.__rclpy_subscription = None | ||
# first item is unknown state | ||
self.__current_state = next(iter(ChangeState.valid_states.values())) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. seems like it'd be more appropriate to explicitly set There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair enough, I think I was imagining that the "first" state might change and this would be more future proof, but that's silly (it was probably late): 6955bc8 |
||
|
||
def _on_transition_event(self, context, msg): | ||
try: | ||
event = StateTransition(action=self, msg=msg) | ||
self.__current_state = ChangeState.valid_states[msg.goal_state.id] | ||
context.emit_event_sync(event) | ||
except Exception as exc: | ||
print("Exception in handling of 'lifecycle.msg.TransitionEvent': {}".format(exc)) | ||
|
||
def _call_change_state(self, request, context: launch.LaunchContext): | ||
while not self.__rclpy_change_state_client.wait_for_service(timeout_sec=1.0): | ||
if context.is_shutdown: | ||
_logger.warn("Abandoning wait for the '{}' service.".format( | ||
self.__rclpy_change_state_client.srv_name)) | ||
return | ||
response = self.__rclpy_change_state_client.call(request) | ||
if not response.success: | ||
_logger.warn("Failed to make transition '{}' for LifecycleNode '{}'".format( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe this should be an error instead of just a warning. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
ChangeState.valid_transitions[request.transition.id], | ||
self.node_name, | ||
)) | ||
|
||
def _on_change_state_event(self, context: launch.LaunchContext) -> None: | ||
typed_event = cast(ChangeState, context.locals.event) | ||
if not typed_event.lifecycle_node_matcher(self): | ||
return None | ||
request = lifecycle_msgs.srv.ChangeState.Request() | ||
request.transition.id = typed_event.transition_id | ||
context.add_completion_future( | ||
context.asyncio_loop.run_in_executor(None, self._call_change_state, request, context)) | ||
|
||
def execute(self, context: launch.LaunchContext) -> Optional[List[Action]]: | ||
""" | ||
Execute the action. | ||
|
||
Delegated to :meth:`launch.actions.ExecuteProcess.execute`. | ||
""" | ||
self._perform_substitutions(context) # ensure self.node_name is expanded | ||
if '<node_name_unspecified>' in self.node_name: | ||
raise RuntimeError('node_name unexpectedly incomplete for lifecycle node') | ||
# Create a subscription to monitor the state changes of the subprocess. | ||
self.__rclpy_subscription = context.locals.launch_ros_node.create_subscription( | ||
lifecycle_msgs.msg.TransitionEvent, | ||
'{}/transition_event'.format(self.node_name), | ||
functools.partial(self._on_transition_event, context)) | ||
# Create a service client to change state on demand. | ||
self.__rclpy_change_state_client = context.locals.launch_ros_node.create_client( | ||
lifecycle_msgs.srv.ChangeState, | ||
'{}/change_state'.format(self.node_name)) | ||
# Register an event handler to change states on a ChangeState lifecycle event. | ||
context.register_event_handler(launch.EventHandler( | ||
matcher=lambda event: isinstance(event, ChangeState), | ||
entities=[launch.actions.OpaqueFunction(function=self._on_change_state_event)], | ||
)) | ||
# Delegate execution to Node and ExecuteProcess. | ||
return super().execute(context) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is not being used FWICT
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right 838242e