-
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
Conversation
735ea98
to
20982c2
Compare
Copy/Paste mistake, correct link is https://github.com/ros2/launch/pull/75/files#diff-2086d091e24f813cafb9e343fb009c64 |
7fa3f8f
to
86a58b1
Compare
Rebased to one commit after #74 was merged. I'll start prepping this for review now. |
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.
partial review
_logger = logging.getLogger(name='launch_ros') | ||
|
||
|
||
class Node(ExecuteProcess): |
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.
I would suggest naming this ExecuteNode instead. To me, seeing something trigger an action of "Node" somewhere like here can mislead people, and I don't see the need to risk the confusion (but there might be one)
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.
The reason I departed from Execute
in these is because of how I planed to reuse them as arguments to the "node container", basically something like this:
LaunchDescription([
ComponentNodeContainer(name='whatever', nodes=[
Node(...),
Node(...),
]),
Node(...),
])
Where the first two Node
would never be visited, and instead would be introspected by the ComponentNodeContainer
to figure out how to load them dynamically, and the last Node
would actually get visited and therefore executed.
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.
Maybe there's a better way to organize the names, with that in mind. I don't know.
I also thought about an Execute
action which would look like:
LaunchDescription([
ComponentNodeContainer(name='whatever', nodes=[
Node(...),
Node(...),
]),
Execute(executable=Node(...), cwd='something'),
])
In which case Node
would no longer inherit from ExecuteProcess
.
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.
Even if Node
doesn't inherit from ExecuteProcess we'd still probably benefit from distinguishing it from something that is actually a node (it is not, in any of these cases). For now, it's namespaced enough that I suppose we can lean on that.
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.
That's part of the reason I use launch.x.Y
in most of the demos, to reinforce those differences.
I also somewhat inconsistently use Launch<thing>
and <thing>
, e.g. LaunchDescription
versus Action
(and not LaunchAction
). I don't have a good reason for the inconsistency, other than I felt that in context launch.Action
was specific enough, but launch.Description
was not (mostly due to where and how it's used in the examples.
It might be something we want to revisit in the near future.
Do you want me to change anything for this pr related to this conversation?
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.
I think for this PR we're good, and then there's a chance we'll rename things in the future.
try: | ||
rclpy.init(args=context.argv) | ||
except RuntimeError as exc: | ||
if 'rcl_init called while already initialized' in str(exc): |
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.
we should make a todo (if we haven't already, apologies if this is something you've already tracked) to add an AlreadyInitializedException to rclpy.
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.
Already made one 😁: ros2/rclpy#190
self.__rclpy_node = rclpy.create_node('launch_ros') | ||
context.extend_globals({ | ||
'ros_startup_action': self, | ||
'rclpy_node': self.__rclpy_node |
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.
I think the usage will be clearer if this is renamed to something like launcher_node
because otherwise being used as context.locals.rclpy_node
you don't really know where it came from/who put it there.
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.
Sounds good 431853f
node_executable: SomeSubstitutionsType, | ||
node_name: Optional[SomeSubstitutionsType] = None, | ||
node_namespace: Optional[SomeSubstitutionsType] = None, | ||
remappings: Optional[Dict[SomeSubstitutionsType, SomeSubstitutionsType]] = None, |
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.
Aren't there cases where the specific order of the remapping rules has to be preserved, because applying them in different orders causes different results? If so, another type would be more appropriate e.g. OrderedDict or list of tuples.
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.
That's true, let me look into that.
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.
So, I can't use OrderedDict as a type annotation with subscripted types for key and value, unfortunately. I don't like using a list of tuples because of how it looks/feels in the examples, but I don't see another way, and creating an ordered dict also uses a list of tuples. So I guess I'll do that, but the demos/examples need to be updated.
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.
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.
], | ||
))) | ||
|
||
# Make the talker node take the 'configure' transition. |
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.
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 comment
The 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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
Checkout 46bd3e1
self.__expanded_node_namespace = perform_substitutions( | ||
context, normalize_to_list_of_substitutions(self.__node_namespace)) | ||
if not self.__expanded_node_namespace.startswith('/'): | ||
self.__expanded_node_namespace = '/' + self.__expanded_node_namespace |
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.
what is the point of adding a leading forward slash if the next line does lstrip('/')
?
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.
No idea, 03e9d07
launch_ros/package.xml
Outdated
<maintainer email="william@osrfoundation.org">William Woodall</maintainer> | ||
<license>Apache License 2.0</license> | ||
|
||
<depend>launch</depend> |
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 also depends on rclpy, lifecycle_msgs, ament_index_python and osrf_pycommon
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.
Yes, but I don't think osrf_pycommon
directly. cd0ebe2
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.
osrf_pycommon's which
is used: https://github.com/ros2/launch/pull/75/files#diff-5faadcc2201b3e21ba8aee1be2d7d50fR30
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.
You were right, I was wrong, here's the fixup: 15ac0c3
Return a LaunchDescription to be included before user descriptions. | ||
|
||
:param: prefix_output_with_name if True, each line of output is prefixed | ||
with the name of the node as `[name] `, else it is printed unmodified |
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.
I think this should be more generic than referencing nodes since it will prefix all processes' output right?
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.
Correct b4b83da
raise | ||
self.__launch_ros_node = rclpy.create_node('launch_ros') | ||
context.extend_globals({ | ||
'ros_startup_action': self, |
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.
what's the intended use of this?
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.
Future use, nothing for now.
from ...actions import LifecycleNode # noqa | ||
|
||
|
||
def matches_node_name(node_name: Text) -> Callable[['LifecycleNode'], bool]: |
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.
it seems like this isn't lifecycle specific (maybe it was at some point but it doesn't look like it's used anymore), and the module could be moved/renamed (maybe in followup)
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.
Well, not lifecycle specific, but there are no events that target Node
right now (there are some that target ExecuteProcess
however), so I put it with the lifecycle events which it is used with. We may need to move it later.
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 comment
The reason will be displayed to describe this comment to others. Learn more.
seems like it'd be more appropriate to explicitly set self.__current_state = ChangeState.valid_states[lifecycle_msgs.msg.State.PRIMARY_STATE_UNKNOWN]
(because we want to do this regardless of dict ordering)
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.
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
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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
def main(argv=sys.argv[1:]): | ||
"""Main.""" | ||
def _on_transition_event(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
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.
🚢
|
Retroactive summary for this feature: add ROS specific features to launch systemPurpose / Use Cases (Why implement this feature?)This was implemented to provide ROS specific actions, substitutions, events, etc... to the launch system and to add default actions which allow monitoring of child nodes with lifecycle. Design (How does it work?)It provides new actions (like It also adds some "default" ROS related actions that are (will be) included in the Inputs / Outputs / API (How to use it?)The API is anything in the https://github.com/ros2/launch/tree/baefc077b062ba5cfaff8a4dbecda7aaaeb4a05a/launch_ros/launch_ros |
Requires #74
Things to have a look at: