Skip to content

Commit adba69e

Browse files
committed
Add ros2 node get_type_description initial implementation
Signed-off-by: Emerson Knapp <[email protected]>
1 parent 4523cc5 commit adba69e

File tree

3 files changed

+159
-0
lines changed

3 files changed

+159
-0
lines changed

ros2node/package.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
<exec_depend>rclpy</exec_depend>
2121
<exec_depend>ros2cli</exec_depend>
22+
<exec_depend>type_description_interfaces</exec_depend>
2223

2324
<test_depend>ament_copyright</test_depend>
2425
<test_depend>ament_flake8</test_depend>
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# Copyright 2023 Open Source Robotics Foundation, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import os
16+
17+
import rclpy
18+
from ros2cli.node import NODE_NAME_PREFIX
19+
from ros2cli.node.strategy import add_arguments
20+
from ros2node.api import (
21+
parse_node_name,
22+
INFO_NONUNIQUE_WARNING_TEMPLATE,
23+
NodeNameCompleter,
24+
)
25+
from ros2node.verb import VerbExtension
26+
from type_description_interfaces.msg import FieldType
27+
from type_description_interfaces.srv import GetTypeDescription
28+
29+
30+
def print_field_type(ft):
31+
type_name = None
32+
type_str = ''
33+
for x, y in FieldType.__dict__.items():
34+
if y == ft.type_id:
35+
type_name = x
36+
break
37+
if not type_name:
38+
raise RuntimeError(f'Could not match FieldType type_id {ft.type_id} to a name')
39+
40+
type_str = type_name
41+
if 'NESTED_TYPE' in type_name:
42+
type_str += f' ({ft.nested_type_name})'
43+
if ft.string_capacity:
44+
type_str += f' - string_capacity {ft.string_capacity}'
45+
if ft.capacity:
46+
type_str += f' - capacity {ft.capacity}'
47+
return type_str
48+
49+
50+
def print_individual_type_description(itd, indent=0):
51+
def p(line):
52+
print(' ' * indent + line)
53+
54+
print()
55+
p(f'{itd.type_name}')
56+
p('Fields:')
57+
indent += 2
58+
for field in itd.fields:
59+
field_string = f'{field.name}: {print_field_type(field.type)}'
60+
if field.default_value:
61+
field_string += f' = {field.default_value}'
62+
p(field_string)
63+
indent -= 2
64+
65+
66+
def print_type_description(td):
67+
print_individual_type_description(td.type_description)
68+
print()
69+
print('Referenced Type Descriptions:')
70+
for rtd in td.referenced_type_descriptions:
71+
print_individual_type_description(rtd, indent=2)
72+
73+
74+
def discover_hash_for_type(node, remote_node_name, type_name):
75+
# TODO(emersonknapp) get hashes from names_and_types when that is implemented
76+
ns = type_name.split('/')[1]
77+
if ns != 'msg':
78+
raise RuntimeError(
79+
f'Currently cannot auto-discover hashes for "{ns}", only "msg". '
80+
'Please provide type_hash value to command.')
81+
remote_node = parse_node_name(remote_node_name)
82+
nt = node.get_publisher_names_and_types_by_node(remote_node.name, remote_node.namespace)
83+
nt += node.get_subscriber_names_and_types_by_node(remote_node.name, remote_node.namespace)
84+
discover_topic = None
85+
for topic, types in nt:
86+
if type_name in types:
87+
discover_topic = topic
88+
if not discover_topic:
89+
raise RuntimeError(
90+
f'Could not find type "{type_name}" advertised as a topic type on node '
91+
f'"{remote_node_name}"')
92+
93+
endpoint_infos = node.get_publishers_info_by_topic(discover_topic)
94+
endpoint_infos += node.get_subscriptions_info_by_topic(discover_topic)
95+
for endpoint_info in endpoint_infos:
96+
if (
97+
endpoint_info.node_name == remote_node.name and
98+
endpoint_info.node_namespace == remote_node.namespace and
99+
endpoint_info.topic_type == type_name
100+
):
101+
return str(endpoint_info.topic_type_hash)
102+
raise RuntimeError(
103+
f'Cound not find endpoint type hash for topic "{discover_topic}"')
104+
105+
106+
class GetTypeDescriptionVerb(VerbExtension):
107+
"""Output information about a node."""
108+
109+
def add_arguments(self, parser, cli_name):
110+
add_arguments(parser)
111+
argument = parser.add_argument(
112+
'node_name',
113+
help='Node name to request information from')
114+
argument.completer = NodeNameCompleter()
115+
parser.add_argument(
116+
'type_name',
117+
help='Interface type to get description of')
118+
parser.add_argument(
119+
'type_hash',
120+
default=None,
121+
nargs='?',
122+
help='Hash string of the type. If not provided, will try to determine automatically.')
123+
parser.add_argument(
124+
'--include-sources', action='store_true',
125+
help='Fetch and display the raw source text as well')
126+
127+
def main(self, *, args):
128+
service_name = f'{args.node_name}/get_type_description'
129+
130+
rclpy.init()
131+
node = rclpy.create_node(f'{NODE_NAME_PREFIX}_td_requester_{os.getpid()}')
132+
cli = node.create_client(GetTypeDescription, '/talker/get_type_description')
133+
if not cli.service_is_ready():
134+
print('Waiting for service to become available...')
135+
if not cli.wait_for_service(timeout_sec=5.0):
136+
raise RuntimeError(f'Service {service_name} not found')
137+
138+
if args.type_hash:
139+
type_hash = args.type_hash
140+
else:
141+
type_hash = discover_hash_for_type(node, args.node_name, args.type_name)
142+
print(f'Requesting type hash {type_hash}')
143+
144+
request = GetTypeDescription.Request()
145+
request.type_name = args.type_name
146+
request.type_hash = type_hash
147+
request.include_type_sources = args.include_sources
148+
print('Sending request...')
149+
future = cli.call_async(request)
150+
rclpy.spin_until_future_complete(node, future)
151+
response = future.result()
152+
if not response.successful:
153+
raise RuntimeError(
154+
f'Failed to get type description: {response.failure_reason}')
155+
156+
print('Response successful:')
157+
print_type_description(response.type_description)

ros2node/setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
'ros2node.verb': [
4343
'info = ros2node.verb.info:InfoVerb',
4444
'list = ros2node.verb.list:ListVerb',
45+
'get-type-description = ros2node.verb.get_type_description:GetTypeDescriptionVerb',
4546
],
4647
}
4748
)

0 commit comments

Comments
 (0)