1212# See the License for the specific language governing permissions and
1313# limitations under the License.
1414
15- from typing import Any
16- from typing import Callable
1715from typing import Optional
1816from typing import TypeVar
1917
@@ -59,6 +57,13 @@ def add_arguments(self, parser, cli_name):
5957 '--csv' , action = 'store_true' ,
6058 help = 'Output all recursive fields separated by commas (e.g. for '
6159 'plotting)' )
60+ parser .add_argument (
61+ '--field' , type = str , default = None ,
62+ help = 'Echo a selected field of a message. '
63+ "Use '.' to select sub-fields. "
64+ 'For example, to echo the position field of a nav_msgs/msg/Odometry message: '
65+ "'ros2 topic echo /odom --field pose.pose.position'" ,
66+ )
6267 parser .add_argument (
6368 '--full-length' , '-f' , action = 'store_true' ,
6469 help = 'Output all elements for arrays, bytes, and string with a '
@@ -78,98 +83,111 @@ def add_arguments(self, parser, cli_name):
7883 '--raw' , action = 'store_true' , help = 'Echo the raw binary representation' )
7984
8085 def main (self , * , args ):
81- return main (args )
86+ # Select print function
87+ self .print_func = _print_yaml
88+ if args .csv :
89+ self .print_func = _print_csv
90+
91+ # Validate field selection
92+ self .field = args .field
93+ if self .field is not None :
94+ self .field = list (filter (None , self .field .split ('.' )))
95+ if not self .field :
96+ raise RuntimeError (f"Invalid field value '{ args .field } '" )
97+
98+ self .truncate_length = args .truncate_length if not args .full_length else None
99+ self .no_arr = args .no_arr
100+ self .no_str = args .no_str
101+
102+ qos_profile = qos_profile_from_short_keys (
103+ args .qos_profile ,
104+ reliability = args .qos_reliability ,
105+ durability = args .qos_durability ,
106+ depth = args .qos_depth ,
107+ history = args .qos_history )
108+
109+ with NodeStrategy (args ) as node :
110+ if args .message_type is None :
111+ message_type = get_msg_class (
112+ node , args .topic_name , include_hidden_topics = True )
113+ else :
114+ try :
115+ message_type = get_message (args .message_type )
116+ except (AttributeError , ModuleNotFoundError , ValueError ):
117+ raise RuntimeError ('The passed message type is invalid' )
118+
119+ if message_type is None :
120+ raise RuntimeError (
121+ 'Could not determine the type for the passed topic' )
122+
123+ self .subscribe_and_spin (
124+ node ,
125+ args .topic_name ,
126+ message_type ,
127+ qos_profile ,
128+ args .lost_messages ,
129+ args .raw )
130+
131+ def subscribe_and_spin (
132+ self ,
133+ node : Node ,
134+ topic_name : str ,
135+ message_type : MsgType ,
136+ qos_profile : QoSProfile ,
137+ report_lost_messages : bool ,
138+ raw : bool
139+ ) -> Optional [str ]:
140+ """Initialize a node with a single subscription and spin."""
141+ event_callbacks = None
142+ if report_lost_messages :
143+ event_callbacks = SubscriptionEventCallbacks (
144+ message_lost = _message_lost_event_callback )
145+ try :
146+ node .create_subscription (
147+ message_type ,
148+ topic_name ,
149+ self ._subscriber_callback ,
150+ qos_profile ,
151+ event_callbacks = event_callbacks ,
152+ raw = raw )
153+ except UnsupportedEventTypeError :
154+ assert report_lost_messages
155+ print (
156+ f"The rmw implementation '{ get_rmw_implementation_identifier ()} '"
157+ ' does not support reporting lost messages'
158+ )
159+ rclpy .spin (node )
82160
161+ def _subscriber_callback (self , msg ):
162+ submsg = msg
163+ if self .field is not None :
164+ for field in self .field :
165+ try :
166+ submsg = getattr (submsg , field )
167+ except AttributeError as ex :
168+ raise RuntimeError (f"Invalid field '{ '.' .join (self .field )} ': { ex } " )
83169
84- def main (args ):
85- if not args .csv :
86- truncate_length = args .truncate_length if not args .full_length else None
87- callback = subscriber_cb (truncate_length , args .no_arr , args .no_str )
88- else :
89- truncate_length = args .truncate_length if not args .full_length else None
90- callback = subscriber_cb_csv (truncate_length , args .no_arr , args .no_str )
91- qos_profile = qos_profile_from_short_keys (
92- args .qos_profile , reliability = args .qos_reliability , durability = args .qos_durability ,
93- depth = args .qos_depth , history = args .qos_history )
94- with NodeStrategy (args ) as node :
95- if args .message_type is None :
96- message_type = get_msg_class (
97- node , args .topic_name , include_hidden_topics = True )
98- else :
99- try :
100- message_type = get_message (args .message_type )
101- except (AttributeError , ModuleNotFoundError , ValueError ):
102- raise RuntimeError ('The passed message type is invalid' )
103-
104- if message_type is None :
105- raise RuntimeError (
106- 'Could not determine the type for the passed topic' )
107-
108- subscriber (
109- node ,
110- args .topic_name ,
111- message_type ,
112- callback ,
113- qos_profile ,
114- args .lost_messages ,
115- args .raw )
116-
117-
118- def subscriber (
119- node : Node ,
120- topic_name : str ,
121- message_type : MsgType ,
122- callback : Callable [[MsgType ], Any ],
123- qos_profile : QoSProfile ,
124- report_lost_messages : bool ,
125- raw : bool
126- ) -> Optional [str ]:
127- """Initialize a node with a single subscription and spin."""
128- event_callbacks = None
129- if report_lost_messages :
130- event_callbacks = SubscriptionEventCallbacks (
131- message_lost = message_lost_event_callback )
132- try :
133- node .create_subscription (
134- message_type ,
135- topic_name ,
136- callback ,
137- qos_profile ,
138- event_callbacks = event_callbacks ,
139- raw = raw )
140- except UnsupportedEventTypeError :
141- assert report_lost_messages
142- print (
143- f"The rmw implementation '{ get_rmw_implementation_identifier ()} '"
144- ' does not support reporting lost messages'
145- )
146- rclpy .spin (node )
170+ self .print_func (submsg , self .truncate_length , self .no_arr , self .no_str )
147171
148172
149- def subscriber_cb (truncate_length , noarr , nostr ):
150- def cb (msg ):
151- nonlocal truncate_length , noarr , nostr
152- if isinstance (msg , bytes ):
153- print (msg , end = '\n ---\n ' )
154- else :
155- print (
156- message_to_yaml (
157- msg , truncate_length = truncate_length , no_arr = noarr , no_str = nostr ),
158- end = '---\n ' )
159- return cb
173+ def _print_yaml (msg , truncate_length , noarr , nostr ):
174+ if hasattr (msg , '__slots__' ):
175+ print (
176+ message_to_yaml (
177+ msg , truncate_length = truncate_length , no_arr = noarr , no_str = nostr ),
178+ end = '---\n ' )
179+ else :
180+ print (msg , end = '\n ---\n ' )
160181
161182
162- def subscriber_cb_csv (truncate_length , noarr , nostr ):
163- def cb (msg ):
164- nonlocal truncate_length , noarr , nostr
165- if isinstance (msg , bytes ):
166- print (msg )
167- else :
168- print (message_to_csv (msg , truncate_length = truncate_length , no_arr = noarr , no_str = nostr ))
169- return cb
183+ def _print_csv (msg , truncate_length , noarr , nostr ):
184+ if hasattr (msg , '__slots__' ):
185+ print (message_to_csv (msg , truncate_length = truncate_length , no_arr = noarr , no_str = nostr ))
186+ else :
187+ print (msg )
170188
171189
172- def message_lost_event_callback (message_lost_status ):
190+ def _message_lost_event_callback (message_lost_status ):
173191 print (
174192 'A message was lost!!!\n \t total count change:'
175193 f'{ message_lost_status .total_count_change } '
0 commit comments