Skip to content

Commit e8a0fb4

Browse files
Enable ros2 topic echo with entries of array fields (#996)
Signed-off-by: Fabian Thomsen <[email protected]>
1 parent a9ef32e commit e8a0fb4

File tree

2 files changed

+80
-3
lines changed

2 files changed

+80
-3
lines changed

ros2topic/ros2topic/verb/echo.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import re
1516
from typing import Optional
1617
from typing import TypeVar
1718

@@ -72,7 +73,9 @@ def add_arguments(self, parser, cli_name):
7273
help='Echo a selected field of a message. '
7374
"Use '.' to select sub-fields. "
7475
'For example, to echo the position field of a nav_msgs/msg/Odometry message: '
75-
"'ros2 topic echo /odom --field pose.pose.position'",
76+
"'ros2 topic echo /odom --field pose.pose.position'. "
77+
'Use the --field option multiple times to echo multiple fields. '
78+
'If the field is an array, use the syntax .[index] to select a single element.'
7679
)
7780
parser.add_argument(
7881
'--full-length', '-f', action='store_true',
@@ -215,12 +218,19 @@ def _timed_out(self):
215218
def _subscriber_callback(self, msg, info):
216219
submsgs = []
217220
if self.fields_list:
221+
# Matches strings exactly in the format "[digits]" (e.g., "[123]")
222+
# and captures the digits as a group
223+
is_indexing = re.compile(r'^\[(\d+)\]$')
218224
for fields in self.fields_list:
219225
submsg = msg
220226
for field in fields:
227+
match = is_indexing.match(field)
221228
try:
222-
submsg = getattr(submsg, field)
223-
except AttributeError as ex:
229+
if match is None:
230+
submsg = getattr(submsg, field)
231+
else:
232+
submsg = submsg[int(match.group(1))]
233+
except (AttributeError, IndexError, TypeError, ValueError) as ex:
224234
raise RuntimeError(f"Invalid field '{'.'.join(fields)}': {ex}")
225235
submsgs.append(submsg)
226236
else:

ros2topic/test/test_cli.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,19 @@ def test_topic_echo_field_nested(self):
616616
), timeout=10)
617617
assert topic_command.wait_for_shutdown(timeout=10)
618618

619+
@launch_testing.markers.retry_on_failure(times=5, delay=1)
620+
def test_topic_echo_field_array(self):
621+
with self.launch_topic_command(
622+
arguments=['echo', '/arrays', '--field', 'float32_values_default.[2]'],
623+
) as topic_command:
624+
assert topic_command.wait_for_output(functools.partial(
625+
launch_testing.tools.expect_output, expected_lines=[
626+
'-1.125',
627+
'---',
628+
], strict=True
629+
), timeout=10)
630+
assert topic_command.wait_for_shutdown(timeout=10)
631+
619632
@launch_testing.markers.retry_on_failure(times=5, delay=1)
620633
def test_topic_echo_multi_fields_nested(self):
621634
with self.launch_topic_command(
@@ -631,6 +644,21 @@ def test_topic_echo_multi_fields_nested(self):
631644
), timeout=10)
632645
assert topic_command.wait_for_shutdown(timeout=10)
633646

647+
@launch_testing.markers.retry_on_failure(times=5, delay=1)
648+
def test_topic_echo_multi_fields_array(self):
649+
with self.launch_topic_command(
650+
arguments=['echo', '/arrays', '--field', 'float32_values_default.[2]', '--field',
651+
'string_values_default.[1]'],
652+
) as topic_command:
653+
assert topic_command.wait_for_output(functools.partial(
654+
launch_testing.tools.expect_output, expected_lines=[
655+
'-1.125',
656+
'max value',
657+
'---',
658+
], strict=True
659+
), timeout=10)
660+
assert topic_command.wait_for_shutdown(timeout=10)
661+
634662
@launch_testing.markers.retry_on_failure(times=5, delay=1)
635663
def test_topic_echo_field_not_a_member(self):
636664
with self.launch_topic_command(
@@ -643,6 +671,45 @@ def test_topic_echo_field_not_a_member(self):
643671
), timeout=10)
644672
assert topic_command.wait_for_shutdown(timeout=10)
645673

674+
@launch_testing.markers.retry_on_failure(times=5, delay=1)
675+
def test_topic_echo_field_array_not_an_array(self):
676+
with self.launch_topic_command(
677+
arguments=['echo', '/arrays', '--field', 'float32_values_default.[0].[0]'],
678+
) as topic_command:
679+
assert topic_command.wait_for_output(functools.partial(
680+
launch_testing.tools.expect_output, expected_lines=[
681+
"Invalid field 'float32_values_default.[0].[0]': invalid index to "
682+
'scalar variable.',
683+
], strict=True
684+
), timeout=10)
685+
assert topic_command.wait_for_shutdown(timeout=10)
686+
687+
@launch_testing.markers.retry_on_failure(times=5, delay=1)
688+
def test_topic_echo_field_array_index_out_of_bounds(self):
689+
with self.launch_topic_command(
690+
arguments=['echo', '/arrays', '--field', 'float32_values_default.[3]'],
691+
) as topic_command:
692+
assert topic_command.wait_for_output(functools.partial(
693+
launch_testing.tools.expect_output, expected_lines=[
694+
"Invalid field 'float32_values_default.[3]': index 3 is out of bounds "
695+
'for axis 0 with size 3',
696+
], strict=True
697+
), timeout=10)
698+
assert topic_command.wait_for_shutdown(timeout=10)
699+
700+
@launch_testing.markers.retry_on_failure(times=5, delay=1)
701+
def test_topic_echo_field_array_no_index(self):
702+
with self.launch_topic_command(
703+
arguments=['echo', '/arrays', '--field', 'float32_values_default.[abc]'],
704+
) as topic_command:
705+
assert topic_command.wait_for_output(functools.partial(
706+
launch_testing.tools.expect_output, expected_lines=[
707+
"Invalid field 'float32_values_default.[abc]': 'numpy.ndarray' object "
708+
"has no attribute '[abc]'",
709+
], strict=True
710+
), timeout=10)
711+
assert topic_command.wait_for_shutdown(timeout=10)
712+
646713
@launch_testing.markers.retry_on_failure(times=5, delay=1)
647714
def test_topic_echo_multi_fields_not_a_member(self):
648715
with self.launch_topic_command(

0 commit comments

Comments
 (0)