mir_bridge.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 import rospy
3 
4 import copy
5 import sys
6 from collections import Iterable
7 
8 from mir_driver import rosbridge
9 from rospy_message_converter import message_converter
10 
11 from actionlib_msgs.msg import GoalID, GoalStatusArray
12 from diagnostic_msgs.msg import DiagnosticArray, DiagnosticStatus
13 from dynamic_reconfigure.msg import Config, ConfigDescription
14 from geometry_msgs.msg import PolygonStamped, Pose, PoseArray, PoseStamped, PoseWithCovarianceStamped, Twist
15 from mir_actions.msg import *
16 from mir_msgs.msg import *
17 from move_base_msgs.msg import MoveBaseActionFeedback, MoveBaseActionGoal, MoveBaseActionResult, MoveBaseFeedback, MoveBaseResult
18 from nav_msgs.msg import GridCells, MapMetaData, OccupancyGrid, Odometry, Path
19 from rosgraph_msgs.msg import Log
20 from sdc21x0.msg import MotorCurrents
21 from sensor_msgs.msg import Imu, LaserScan, PointCloud2, Range
22 from std_msgs.msg import Float64, String
23 from tf.msg import tfMessage
24 from visualization_msgs.msg import Marker, MarkerArray
25 
26 tf_prefix = ''
27 
28 class TopicConfig(object):
29  def __init__(self, topic, topic_type, latch=False, dict_filter=None):
30  self.topic = topic
31  self.topic_type = topic_type
32  self.latch = latch
33  self.dict_filter = dict_filter
34 
35 # remap mir_actions/MirMoveBaseAction => move_base_msgs/MoveBaseAction
37  # filter out slots from the dict that are not in our message definition
38  # e.g., MiRMoveBaseFeedback has the field "state", which MoveBaseFeedback doesn't
39  filtered_msg_dict = copy.deepcopy(msg_dict)
40  filtered_msg_dict['feedback'] = {key: msg_dict['feedback'][key] for key in MoveBaseFeedback.__slots__}
41  return filtered_msg_dict
42 
44  filtered_msg_dict = copy.deepcopy(msg_dict)
45  filtered_msg_dict['result'] = {key: msg_dict['result'][key] for key in MoveBaseResult.__slots__}
46  return filtered_msg_dict
47 
48 def _tf_dict_filter(msg_dict):
49  filtered_msg_dict = copy.deepcopy(msg_dict)
50  for transform in filtered_msg_dict['transforms']:
51  transform['child_frame_id'] = tf_prefix + '/' + transform['child_frame_id'].strip('/')
52  return filtered_msg_dict
53 
55  #filtered_msg_dict = copy.deepcopy(msg_dict)
56  if not isinstance(msg_dict, dict): # can happen during recursion
57  return
58  for (key, value) in msg_dict.iteritems():
59  if key == 'header':
60  try:
61  # prepend frame_id
62  frame_id = value['frame_id'].strip('/')
63  if (frame_id != 'map'):
64  # prepend tf_prefix, then remove leading '/' (e.g., when tf_prefix is empty)
65  value['frame_id'] = (tf_prefix + '/' + frame_id).strip('/')
66  else:
67  value['frame_id'] = frame_id
68 
69  except TypeError:
70  pass # value is not a dict
71  except KeyError:
72  pass # value doesn't have key 'frame_id'
73  elif isinstance(value, dict):
75  elif isinstance(value, Iterable): # an Iterable other than dict (e.g., a list)
76  for item in value:
78  return msg_dict
79 
81  #filtered_msg_dict = copy.deepcopy(msg_dict)
82  if not isinstance(msg_dict, dict): # can happen during recursion
83  return
84  for (key, value) in msg_dict.iteritems():
85  if key == 'header':
86  try:
87  # remove frame_id
88  s = value['frame_id'].strip('/')
89  if s.find(tf_prefix) == 0:
90  value['frame_id'] = (s[len(tf_prefix):]).strip('/') # strip off tf_prefix, then strip leading '/'
91  except TypeError:
92  pass # value is not a dict
93  except KeyError:
94  pass # value doesn't have key 'frame_id'
95  elif isinstance(value, dict):
97  elif isinstance(value, Iterable): # an Iterable other than dict (e.g., a list)
98  for item in value:
100  return msg_dict
101 
102 
103 
104 # topics we want to publish to ROS (and subscribe to from the MiR)
105 PUB_TOPICS = [
106  TopicConfig('LightCtrl/us_list', Range),
107  TopicConfig('MC/currents', MotorCurrents),
108  TopicConfig('MissionController/CheckArea/visualization_marker', Marker),
109  TopicConfig('SickPLC/parameter_descriptions', ConfigDescription),
110  TopicConfig('SickPLC/parameter_updates', Config),
111  TopicConfig('amcl_pose', PoseWithCovarianceStamped),
112  TopicConfig('b_raw_scan', LaserScan),
113  TopicConfig('b_scan', LaserScan),
114  TopicConfig('camera_floor/background', PointCloud2),
115  TopicConfig('camera_floor/depth/parameter_descriptions', ConfigDescription),
116  TopicConfig('camera_floor/depth/parameter_updates', Config),
117  TopicConfig('camera_floor/depth/points', PointCloud2),
118  TopicConfig('camera_floor/filter/parameter_descriptions', ConfigDescription),
119  TopicConfig('camera_floor/filter/parameter_updates', Config),
120  TopicConfig('camera_floor/floor', PointCloud2),
121  TopicConfig('camera_floor/obstacles', PointCloud2),
122  TopicConfig('camera_floor/transform/parameter_descriptions', ConfigDescription),
123  TopicConfig('camera_floor/transform/parameter_updates', Config),
124  TopicConfig('check_area/polygon', PolygonStamped),
125  TopicConfig('diagnostics', DiagnosticArray),
126  TopicConfig('diagnostics_agg', DiagnosticArray),
127  TopicConfig('diagnostics_toplevel_state', DiagnosticStatus),
128  TopicConfig('f_raw_scan', LaserScan),
129  TopicConfig('f_scan', LaserScan),
130  TopicConfig('imu_data', Imu), # not available in simulation
131  TopicConfig('laser_back/driver/parameter_descriptions', ConfigDescription),
132  TopicConfig('laser_back/driver/parameter_updates', Config),
133  TopicConfig('laser_back/transform/parameter_descriptions', ConfigDescription),
134  TopicConfig('laser_back/transform/parameter_updates', Config),
135  TopicConfig('laser_front/driver/parameter_descriptions', ConfigDescription),
136  TopicConfig('laser_front/driver/parameter_updates', Config),
137  TopicConfig('laser_front/transform/parameter_descriptions', ConfigDescription),
138  TopicConfig('laser_front/transform/parameter_updates', Config),
139  TopicConfig('map', OccupancyGrid, latch=True),
140  TopicConfig('map_metadata', MapMetaData),
141  TopicConfig('mir_amcl/parameter_descriptions', ConfigDescription),
142  TopicConfig('mir_amcl/parameter_updates', Config),
143  TopicConfig('mir_amcl/selected_points', PointCloud2),
144  TopicConfig('mir_log', Log),
145  TopicConfig('mir_serial_button', Serial),
146  TopicConfig('mir_sound', String),
147  TopicConfig('mir_status', MirStatus),
148  TopicConfig('mir_status_msg', String),
149 # TopicConfig('mirspawn/node_events', LaunchItem),
150  TopicConfig('mirwebapp/grid_map_metadata', LocalMapStat),
151  TopicConfig('mirwebapp/laser_map_metadata', LocalMapStat),
152 ## TopicConfig('move_base/feedback', MirMoveBaseActionFeedback),
153 ## TopicConfig('move_base/result', MirMoveBaseActionResult),
154  TopicConfig('move_base/feedback', MoveBaseActionFeedback, dict_filter=_move_base_feedback_dict_filter), # really mir_actions/MirMoveBaseActionFeedback
155  TopicConfig('move_base/result', MoveBaseActionResult, dict_filter=_move_base_result_dict_filter), # really mir_actions/MirMoveBaseActionResult
156  TopicConfig('move_base/status', GoalStatusArray),
157 # TopicConfig('move_base_node/MIRPlannerROS/cost_cloud', PointCloud2),
158 # TopicConfig('move_base_node/MIRPlannerROS/global_plan', Path),
159 # TopicConfig('move_base_node/MIRPlannerROS/len_to_goal', Float64),
160  TopicConfig('move_base_node/MIRPlannerROS/local_plan', Path),
161 # TopicConfig('move_base_node/MIRPlannerROS/parameter_descriptions', ConfigDescription),
162 # TopicConfig('move_base_node/MIRPlannerROS/parameter_updates', Config),
163  TopicConfig('move_base_node/SBPLLatticePlanner/plan', Path),
164 # TopicConfig('move_base_node/SBPLLatticePlanner/visualization_marker', MarkerArray),
165  TopicConfig('move_base_node/current_goal', PoseStamped),
166 # TopicConfig('move_base_node/global_costmap/forbidden_area', GridCells),
167 # TopicConfig('move_base_node/global_costmap/inflated_obstacles', GridCells),
168 # TopicConfig('move_base_node/global_costmap/obstacles', GridCells),
169 # TopicConfig('move_base_node/global_costmap/parameter_descriptions', ConfigDescription),
170 # TopicConfig('move_base_node/global_costmap/parameter_updates', Config),
171 # TopicConfig('move_base_node/global_costmap/robot_footprint', PolygonStamped),
172 # TopicConfig('move_base_node/global_costmap/unknown_space', GridCells),
173 # TopicConfig('move_base_node/global_plan', Path),
174 # TopicConfig('move_base_node/local_costmap/forbidden_area', GridCells),
175  TopicConfig('move_base_node/local_costmap/inflated_obstacles', GridCells),
176  TopicConfig('move_base_node/local_costmap/obstacles', GridCells),
177 # TopicConfig('move_base_node/local_costmap/parameter_descriptions', ConfigDescription),
178 # TopicConfig('move_base_node/local_costmap/parameter_updates', Config),
179  TopicConfig('move_base_node/local_costmap/robot_footprint', PolygonStamped),
180 # TopicConfig('move_base_node/local_costmap/unknown_space', GridCells),
181 # TopicConfig('move_base_node/mir_escape_recovery/visualization_marker', Marker),
182 # TopicConfig('move_base_node/parameter_descriptions', ConfigDescription),
183 # TopicConfig('move_base_node/parameter_updates', Config),
184  TopicConfig('odom_comb', Odometry), # odom_comb on real robot, odom on simulator
185  TopicConfig('odom_enc', Odometry),
186  TopicConfig('particlecloud', PoseArray),
187  TopicConfig('relative_move_action/feedback', RelativeMoveActionFeedback),
188  TopicConfig('relative_move_action/result', RelativeMoveActionResult),
189  TopicConfig('relative_move_action/status', GoalStatusArray),
190  TopicConfig('relative_move_node/parameter_descriptions', ConfigDescription),
191  TopicConfig('relative_move_node/parameter_updates', Config),
192  TopicConfig('relative_move_node/time_to_coll', Float64),
193  TopicConfig('relative_move_node/visualization_marker', Marker),
194  TopicConfig('robot_mode', RobotMode),
195  TopicConfig('robot_pose', Pose),
196  TopicConfig('robot_state', RobotState),
197  TopicConfig('rosout', Log),
198  TopicConfig('rosout_agg', Log),
199  TopicConfig('scan', LaserScan),
200  TopicConfig('scan_filter/visualization_marker', Marker),
201  TopicConfig('tf', tfMessage, dict_filter=_tf_dict_filter),
202  TopicConfig('transform_footprint/parameter_descriptions', ConfigDescription),
203  TopicConfig('transform_footprint/parameter_updates', Config),
204  TopicConfig('transform_imu/parameter_descriptions', ConfigDescription),
205  TopicConfig('transform_imu/parameter_updates', Config)]
206 
207 # topics we want to subscribe to from ROS (and publish to the MiR)
208 SUB_TOPICS = [TopicConfig('cmd_vel', Twist),
209  TopicConfig('initialpose', PoseWithCovarianceStamped),
210  TopicConfig('light_cmd', String),
211  TopicConfig('mir_cmd', String),
212  TopicConfig('move_base/cancel', GoalID),
213 ## TopicConfig('move_base/goal', MirMoveBaseActionGoal),
214  TopicConfig('move_base/goal', MoveBaseActionGoal), # really mir_actions/MirMoveBaseActionGoal
215  TopicConfig('move_base_simple/goal', PoseStamped),
216  TopicConfig('relative_move_action/cancel', GoalID),
217  TopicConfig('relative_move_action/goal', RelativeMoveActionGoal)]
218 
219 class PublisherWrapper(rospy.SubscribeListener):
220  def __init__(self, topic_config, robot):
221  self.topic_config = topic_config
222  self.robot = robot
223  self.connected = False
224  self.pub = rospy.Publisher(name=topic_config.topic,
225  data_class=topic_config.topic_type,
226  subscriber_listener=self,
227  latch=topic_config.latch,
228  queue_size=1)
229  rospy.loginfo("[%s] publishing topic '%s' [%s]",
230  rospy.get_name(), topic_config.topic, topic_config.topic_type._type)
231  # latched topics must be subscribed immediately
232  if topic_config.latch:
233  self.peer_subscribe(None, None, None)
234 
235 
236  def peer_subscribe(self, topic_name, topic_publish, peer_publish):
237  if not self.connected:
238  self.connected = True
239  rospy.loginfo("[%s] starting to stream messages on topic '%s'", rospy.get_name(), self.topic_config.topic)
240  self.robot.subscribe(topic=('/' + self.topic_config.topic), callback=self.callback)
241 
242  def peer_unsubscribe(self, topic_name, num_peers):
243  pass
244 ## doesn't work: once ubsubscribed, robot doesn't let us subscribe again
245 # if self.connected and self.pub.get_num_connections() == 0 and not self.topic_config.latch:
246 # self.connected = False
247 # rospy.loginfo("[%s] stopping to stream messages on topic '%s'", rospy.get_name(), self.topic_config.topic)
248 # self.robot.unsubscribe(topic=('/' + self.topic_config.topic))
249 
250  def callback(self, msg_dict):
251  msg_dict = _prepend_tf_prefix_dict_filter(msg_dict)
252  if self.topic_config.dict_filter is not None:
253  msg_dict = self.topic_config.dict_filter(msg_dict)
254  msg = message_converter.convert_dictionary_to_ros_message(self.topic_config.topic_type._type, msg_dict)
255  self.pub.publish(msg)
256 
257 class SubscriberWrapper(object):
258  def __init__(self, topic_config, robot):
259  self.topic_config = topic_config
260  self.robot = robot
261  self.sub = rospy.Subscriber(name=topic_config.topic,
262  data_class=topic_config.topic_type,
263  callback=self.callback,
264  queue_size=1)
265  rospy.loginfo("[%s] subscribing to topic '%s' [%s]",
266  rospy.get_name(), topic_config.topic, topic_config.topic_type._type)
267 
268  def callback(self, msg):
269  msg_dict = message_converter.convert_ros_message_to_dictionary(msg)
270  msg_dict = _remove_tf_prefix_dict_filter(msg_dict)
271  if self.topic_config.dict_filter is not None:
272  msg_dict = self.topic_config.dict_filter(msg_dict)
273  self.robot.publish('/' + self.topic_config.topic, msg_dict)
274 
275 class MiR100Bridge(object):
276  def __init__(self):
277  try:
278  hostname = rospy.get_param('~hostname')
279  except KeyError:
280  rospy.logfatal('[%s] parameter "hostname" is not set!', rospy.get_name())
281  sys.exit(-1)
282  port = rospy.get_param('~port', 9090)
283 
284  global tf_prefix
285  tf_prefix = rospy.get_param('~tf_prefix', '').strip('/')
286 
287  rospy.loginfo('[%s] trying to connect to %s:%i...', rospy.get_name(), hostname, port)
288  self.robot = rosbridge.RosbridgeSetup(hostname, port)
289 
290  r = rospy.Rate(10)
291  i = 1
292  while not self.robot.is_connected():
293  if rospy.is_shutdown():
294  sys.exit(0)
295  if self.robot.is_errored():
296  rospy.logfatal('[%s] connection error to %s:%i, giving up!', rospy.get_name(), hostname, port)
297  sys.exit(-1)
298  if i % 10 == 0:
299  rospy.logwarn('[%s] still waiting for connection to %s:%i...', rospy.get_name(), hostname, port)
300  i += 1
301  r.sleep()
302 
303  rospy.loginfo('[%s] ... connected.', rospy.get_name())
304 
305  topics = self.get_topics()
306  published_topics = [topic_name for (topic_name, _, has_publishers, _) in topics if has_publishers]
307  subscribed_topics = [topic_name for (topic_name, _, _, has_subscribers) in topics if has_subscribers]
308 
309  for pub_topic in PUB_TOPICS:
310  PublisherWrapper(pub_topic, self.robot)
311  if ('/' + pub_topic.topic) not in published_topics:
312  rospy.logwarn("[%s] topic '%s' is not published by the MiR!", rospy.get_name(), pub_topic.topic)
313 
314  for sub_topic in SUB_TOPICS:
315  SubscriberWrapper(sub_topic, self.robot)
316  if ('/' + sub_topic.topic) not in subscribed_topics:
317  rospy.logwarn("[%s] topic '%s' is not yet subscribed to by the MiR!", rospy.get_name(), sub_topic.topic)
318 
319  def get_topics(self):
320  srv_response = self.robot.callService('/rosapi/topics', msg={})
321  topic_names = sorted(srv_response['topics'])
322  topics = []
323 
324  for topic_name in topic_names:
325  srv_response = self.robot.callService("/rosapi/topic_type", msg={'topic': topic_name})
326  topic_type = srv_response['type']
327 
328  srv_response = self.robot.callService("/rosapi/publishers", msg={'topic': topic_name})
329  has_publishers = True if len(srv_response['publishers']) > 0 else False
330 
331  srv_response = self.robot.callService("/rosapi/subscribers", msg={'topic': topic_name})
332  has_subscribers = True if len(srv_response['subscribers']) > 0 else False
333 
334  topics.append([topic_name, topic_type, has_publishers, has_subscribers])
335 
336  print 'Publishers:'
337  for (topic_name, topic_type, has_publishers, has_subscribers) in topics:
338  if has_publishers:
339  print ' * %s [%s]' % (topic_name, topic_type)
340 
341  print '\nSubscribers:'
342  for (topic_name, topic_type, has_publishers, has_subscribers) in topics:
343  if has_subscribers:
344  print ' * %s [%s]' % (topic_name, topic_type)
345 
346  return topics
347 
348 
349 def main():
350  rospy.init_node('mir_bridge')
351  MiR100Bridge()
352  rospy.spin()
353 
354 if __name__ == '__main__':
355  try:
356  main()
357  except rospy.ROSInterruptException:
358  pass
def peer_subscribe(self, topic_name, topic_publish, peer_publish)
Definition: mir_bridge.py:236
def _move_base_feedback_dict_filter(msg_dict)
Definition: mir_bridge.py:36
def __init__(self, topic_config, robot)
Definition: mir_bridge.py:258
def __init__(self, topic_config, robot)
Definition: mir_bridge.py:220
def __init__(self, topic, topic_type, latch=False, dict_filter=None)
Definition: mir_bridge.py:29
def peer_unsubscribe(self, topic_name, num_peers)
Definition: mir_bridge.py:242
def callback(self, msg_dict)
doesn't work: once ubsubscribed, robot doesn't let us subscribe again if self.connected and self...
Definition: mir_bridge.py:250
def main()
Definition: mir_bridge.py:349
def _prepend_tf_prefix_dict_filter(msg_dict)
Definition: mir_bridge.py:54
def _tf_dict_filter(msg_dict)
Definition: mir_bridge.py:48
def _move_base_result_dict_filter(msg_dict)
Definition: mir_bridge.py:43
def _remove_tf_prefix_dict_filter(msg_dict)
Definition: mir_bridge.py:80


mir_driver
Author(s): Martin Günther
autogenerated on Sun Feb 14 2021 03:40:16