Skip to content

Commit 4e51c02

Browse files
authored
support ros2 topic pub yaml file input (#925)
Signed-off-by: Tomoya Fujita <[email protected]>
1 parent a91bbc1 commit 4e51c02

File tree

3 files changed

+101
-9
lines changed

3 files changed

+101
-9
lines changed

Diff for: ros2topic/ros2topic/verb/pub.py

+52-9
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ def add_arguments(self, parser, cli_name):
7070
group.add_argument(
7171
'--stdin', action='store_true',
7272
help='Read values from standard input')
73+
group.add_argument(
74+
'--yaml-file', type=str, default=None,
75+
help='YAML file that has message contents, '
76+
'e.g STDOUT from ros2 topic echo <topic>')
7377
parser.add_argument(
7478
'-r', '--rate', metavar='N', type=positive_float, default=1.0,
7579
help='Publishing rate in Hz (default: 1)')
@@ -131,6 +135,7 @@ def main(args):
131135
args.message_type,
132136
args.topic_name,
133137
values,
138+
args.yaml_file,
134139
1. / args.rate,
135140
args.print,
136141
times,
@@ -146,6 +151,7 @@ def publisher(
146151
message_type: MsgType,
147152
topic_name: str,
148153
values: dict,
154+
yaml_file: str,
149155
period: float,
150156
print_nth: int,
151157
times: int,
@@ -159,9 +165,14 @@ def publisher(
159165
msg_module = get_message(message_type)
160166
except (AttributeError, ModuleNotFoundError, ValueError):
161167
raise RuntimeError('The passed message type is invalid')
162-
values_dictionary = yaml.safe_load(values)
163-
if not isinstance(values_dictionary, dict):
164-
return 'The passed value needs to be a dictionary in YAML format'
168+
169+
msg_reader = None
170+
if yaml_file:
171+
msg_reader = read_msg_from_yaml(yaml_file)
172+
else:
173+
values_dictionary = yaml.safe_load(values)
174+
if not isinstance(values_dictionary, dict):
175+
return 'The passed value needs to be a dictionary in YAML format'
165176

166177
pub = node.create_publisher(msg_module, topic_name, qos_profile)
167178

@@ -184,15 +195,38 @@ def publisher(
184195
total_wait_time += DEFAULT_WAIT_TIME
185196

186197
msg = msg_module()
187-
try:
188-
timestamp_fields = set_message_fields(
189-
msg, values_dictionary, expand_header_auto=True, expand_time_now=True)
190-
except Exception as e:
191-
return 'Failed to populate field: {0}'.format(e)
198+
timestamp_fields = None
199+
200+
if not msg_reader:
201+
# Set the static message from specified values once
202+
try:
203+
timestamp_fields = set_message_fields(
204+
msg, values_dictionary, expand_header_auto=True, expand_time_now=True)
205+
except Exception as e:
206+
return 'Failed to populate field: {0}'.format(e)
207+
192208
print('publisher: beginning loop')
193209
count = 0
210+
more_message = True
194211

195212
def timer_callback():
213+
if msg_reader:
214+
# Try to read out the contents for each message
215+
try:
216+
one_msg = next(msg_reader)
217+
if not isinstance(one_msg, dict):
218+
print('The contents in YAML file need to be a YAML format')
219+
except StopIteration:
220+
nonlocal more_message
221+
more_message = False
222+
return
223+
# Set the message with contents
224+
try:
225+
nonlocal timestamp_fields
226+
timestamp_fields = set_message_fields(
227+
msg, one_msg, expand_header_auto=True, expand_time_now=True)
228+
except Exception as e:
229+
return 'Failed to populate field: {0}'.format(e)
196230
stamp_now = node.get_clock().now().to_msg()
197231
for field_setter in timestamp_fields:
198232
field_setter(stamp_now)
@@ -205,11 +239,20 @@ def timer_callback():
205239
timer_callback()
206240
if times != 1:
207241
timer = node.create_timer(period, timer_callback)
208-
while times == 0 or count < times:
242+
while (times == 0 or count < times) and more_message:
209243
rclpy.spin_once(node)
210244
# give some time for the messages to reach the wire before exiting
211245
time.sleep(keep_alive)
212246
node.destroy_timer(timer)
213247
else:
214248
# give some time for the messages to reach the wire before exiting
215249
time.sleep(keep_alive)
250+
251+
252+
def read_msg_from_yaml(yaml_file):
253+
with open(yaml_file, 'r') as f:
254+
for document in yaml.load_all(f, Loader=yaml.FullLoader):
255+
if document is None:
256+
continue # Skip if there's no more document
257+
258+
yield document

Diff for: ros2topic/test/resources/chatter.yaml

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
data: 'Hello ROS Users'
3+
---
4+
---
5+
data: Hello ROS Developers
6+
---
7+
data: Hello ROS Developers
8+
---
9+
---
10+
data: 'Hello ROS Users'

Diff for: ros2topic/test/test_echo_pub.py

+39
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414

1515
import functools
16+
import pathlib
1617
import sys
1718
import unittest
1819

@@ -49,6 +50,8 @@
4950
TEST_NODE = 'cli_echo_pub_test_node'
5051
TEST_NAMESPACE = 'cli_echo_pub'
5152

53+
TEST_RESOURCES_DIR = pathlib.Path(__file__).resolve().parent / 'resources'
54+
5255

5356
@pytest.mark.rostest
5457
@launch_testing.markers.keep_alive
@@ -289,6 +292,42 @@ def test_pub_times(self, launch_service, proc_info, proc_output):
289292
strict=True
290293
)
291294

295+
@launch_testing.markers.retry_on_failure(times=5)
296+
def test_pub_yaml(self, launch_service, proc_info, proc_output):
297+
command_action = ExecuteProcess(
298+
# yaml file prevails to the values 'data: hello'
299+
cmd=(['ros2', 'topic', 'pub', '/clitest/topic/chatter',
300+
'std_msgs/String', '--yaml-file',
301+
str(TEST_RESOURCES_DIR / 'chatter.yaml')]),
302+
additional_env={
303+
'PYTHONUNBUFFERED': '1'
304+
},
305+
output='screen'
306+
)
307+
with launch_testing.tools.launch_process(
308+
launch_service, command_action, proc_info, proc_output,
309+
output_filter=launch_testing_ros.tools.basic_output_filter(
310+
filtered_rmw_implementation=get_rmw_implementation_identifier()
311+
)
312+
) as command:
313+
assert command.wait_for_shutdown(timeout=10)
314+
assert command.exit_code == launch_testing.asserts.EXIT_OK
315+
assert launch_testing.tools.expect_output(
316+
expected_lines=[
317+
'publisher: beginning loop',
318+
"publishing #1: std_msgs.msg.String(data='Hello ROS Users')",
319+
'',
320+
"publishing #2: std_msgs.msg.String(data='Hello ROS Developers')",
321+
'',
322+
"publishing #3: std_msgs.msg.String(data='Hello ROS Developers')",
323+
'',
324+
"publishing #4: std_msgs.msg.String(data='Hello ROS Users')",
325+
'',
326+
],
327+
text=command.output,
328+
strict=True
329+
)
330+
292331
@launch_testing.markers.retry_on_failure(times=5)
293332
def test_echo_basic(self, launch_service, proc_info, proc_output):
294333
params = [

0 commit comments

Comments
 (0)