6 from collections
import Iterable
8 from mir_driver
import rosbridge
9 from rospy_message_converter
import message_converter
11 from actionlib
import SimpleActionClient
12 import actionlib_msgs.msg
13 import diagnostic_msgs.msg
14 import dynamic_reconfigure.msg
15 import geometry_msgs.msg
16 import mir_actions.msg
18 import move_base_msgs.msg
20 import rosgraph_msgs.msg
22 import sensor_msgs.msg
25 import visualization_msgs.msg
27 from collections
import OrderedDict
30 static_transforms = OrderedDict()
34 def __init__(self, topic, topic_type, latch=False, dict_filter=None):
43 filtered_msg_dict = copy.deepcopy(msg_dict)
44 filtered_msg_dict[
'goal'][
'move_task'] = mir_actions.msg.MirMoveBaseGoal.GLOBAL_MOVE
45 filtered_msg_dict[
'goal'][
'goal_dist_threshold'] = 0.25
46 filtered_msg_dict[
'goal'][
'clear_costmaps'] =
True 47 return filtered_msg_dict
53 filtered_msg_dict = copy.deepcopy(msg_dict)
54 filtered_msg_dict[
'feedback'] = {
55 key: msg_dict[
'feedback'][key]
for key
in move_base_msgs.msg.MoveBaseFeedback.__slots__
57 return filtered_msg_dict
61 filtered_msg_dict = copy.deepcopy(msg_dict)
62 filtered_msg_dict[
'result'] = {key: msg_dict[
'result'][key]
for key
in move_base_msgs.msg.MoveBaseResult.__slots__}
63 return filtered_msg_dict
68 Converts a geometry_msgs/Twist message dict (as sent from the ROS side) to 69 a geometry_msgs/TwistStamped message dict (as expected by the MiR on 70 software version >=2.7). 72 header = std_msgs.msg.Header(frame_id=
'', stamp=rospy.Time.now())
74 'header': message_converter.convert_ros_message_to_dictionary(header),
75 'twist': copy.deepcopy(msg_dict),
77 return filtered_msg_dict
81 filtered_msg_dict = copy.deepcopy(msg_dict)
82 for transform
in filtered_msg_dict[
'transforms']:
83 transform[
'child_frame_id'] = tf_prefix +
'/' + transform[
'child_frame_id'].strip(
'/')
84 return filtered_msg_dict
89 The tf_static topic needs special handling. Publishers on tf_static are *latched*, which means that the ROS master 90 caches the last message that was sent by each publisher on that topic, and will forward it to new subscribers. 91 However, since the mir_driver node appears to the ROS master as a single node with a single publisher on tf_static, 92 and there are multiple actual publishers hiding behind it on the MiR side, only one of those messages will be 93 cached. Therefore, we need to implement the caching ourselves and make sure that we always publish the full set of 94 transforms as a single message. 96 global static_transforms
104 for transform
in filtered_msg_dict[
'transforms']:
105 key = transform[
'child_frame_id']
107 "[%s] tf_static: updated transform %s->%s.",
109 transform[
'header'][
'frame_id'],
110 transform[
'child_frame_id'],
112 static_transforms[key] = transform
115 filtered_msg_dict[
'transforms'] = static_transforms.values()
117 "[%s] tf_static: sent %i transforms: %s",
119 len(static_transforms),
120 str(static_transforms.keys()),
122 return filtered_msg_dict
127 if not isinstance(msg_dict, dict):
129 for (key, value)
in msg_dict.iteritems():
133 frame_id = value[
'frame_id'].strip(
'/')
134 if frame_id !=
'map':
136 value[
'frame_id'] = (tf_prefix +
'/' + frame_id).strip(
'/')
138 value[
'frame_id'] = frame_id
144 elif isinstance(value, dict):
146 elif isinstance(value, Iterable):
154 if not isinstance(msg_dict, dict):
156 for (key, value)
in msg_dict.iteritems():
160 s = value[
'frame_id'].strip(
'/')
161 if s.find(tf_prefix) == 0:
162 value[
'frame_id'] = (s[len(tf_prefix) :]).strip(
'/')
167 elif isinstance(value, dict):
169 elif isinstance(value, Iterable):
179 TopicConfig(
'LightCtrl/us_list', sensor_msgs.msg.Range),
182 TopicConfig(
'MC/currents', sdc21x0.msg.MotorCurrents),
184 TopicConfig(
'MissionController/CheckArea/visualization_marker', visualization_msgs.msg.Marker),
187 TopicConfig(
'SickPLC/parameter_descriptions', dynamic_reconfigure.msg.ConfigDescription),
188 TopicConfig(
'SickPLC/parameter_updates', dynamic_reconfigure.msg.Config),
190 TopicConfig(
'amcl_pose', geometry_msgs.msg.PoseWithCovarianceStamped),
191 TopicConfig(
'b_raw_scan', sensor_msgs.msg.LaserScan),
193 TopicConfig(
'camera_floor/background', sensor_msgs.msg.PointCloud2),
194 TopicConfig(
'camera_floor/depth/parameter_descriptions', dynamic_reconfigure.msg.ConfigDescription),
195 TopicConfig(
'camera_floor/depth/parameter_updates', dynamic_reconfigure.msg.Config),
196 TopicConfig(
'camera_floor/depth/points', sensor_msgs.msg.PointCloud2),
197 TopicConfig(
'camera_floor/filter/visualization_marker', visualization_msgs.msg.Marker),
198 TopicConfig(
'camera_floor/floor', sensor_msgs.msg.PointCloud2),
199 TopicConfig(
'camera_floor/obstacles', sensor_msgs.msg.PointCloud2),
200 TopicConfig(
'check_area/polygon', geometry_msgs.msg.PolygonStamped),
207 TopicConfig(
'diagnostics', diagnostic_msgs.msg.DiagnosticArray),
208 TopicConfig(
'diagnostics_agg', diagnostic_msgs.msg.DiagnosticArray),
209 TopicConfig(
'diagnostics_toplevel_state', diagnostic_msgs.msg.DiagnosticStatus),
210 TopicConfig(
'f_raw_scan', sensor_msgs.msg.LaserScan),
213 TopicConfig(
'laser_back/driver/parameter_descriptions', dynamic_reconfigure.msg.ConfigDescription),
214 TopicConfig(
'laser_back/driver/parameter_updates', dynamic_reconfigure.msg.Config),
215 TopicConfig(
'laser_front/driver/parameter_descriptions', dynamic_reconfigure.msg.ConfigDescription),
216 TopicConfig(
'laser_front/driver/parameter_updates', dynamic_reconfigure.msg.Config),
218 TopicConfig(
'/map', nav_msgs.msg.OccupancyGrid, latch=
True),
219 TopicConfig(
'/map_metadata', nav_msgs.msg.MapMetaData),
229 TopicConfig(
'mir_amcl/parameter_descriptions', dynamic_reconfigure.msg.ConfigDescription),
230 TopicConfig(
'mir_amcl/parameter_updates', dynamic_reconfigure.msg.Config),
231 TopicConfig(
'mir_amcl/selected_points', sensor_msgs.msg.PointCloud2),
234 TopicConfig(
'mir_status_msg', std_msgs.msg.String),
236 TopicConfig(
'mirwebapp/grid_map_metadata', mir_msgs.msg.LocalMapStat),
237 TopicConfig(
'mirwebapp/laser_map_metadata', mir_msgs.msg.LocalMapStat),
241 'move_base/feedback', move_base_msgs.msg.MoveBaseActionFeedback, dict_filter=_move_base_feedback_dict_filter
244 TopicConfig(
'move_base/result', move_base_msgs.msg.MoveBaseActionResult, dict_filter=_move_base_result_dict_filter),
245 TopicConfig(
'move_base/status', actionlib_msgs.msg.GoalStatusArray),
249 TopicConfig(
'move_base_node/MIRPlannerROS/local_plan', nav_msgs.msg.Path),
252 TopicConfig(
'move_base_node/MIRPlannerROS/updated_global_plan', mir_msgs.msg.PlanSegments),
254 TopicConfig(
'move_base_node/SBPLLatticePlanner/plan', nav_msgs.msg.Path),
259 TopicConfig(
'move_base_node/current_goal', geometry_msgs.msg.PoseStamped),
267 TopicConfig(
'move_base_node/local_costmap/inflated_obstacles', nav_msgs.msg.GridCells),
268 TopicConfig(
'move_base_node/local_costmap/obstacles', nav_msgs.msg.GridCells),
271 TopicConfig(
'move_base_node/local_costmap/robot_footprint', geometry_msgs.msg.PolygonStamped),
277 TopicConfig(
'move_base_node/time_to_coll', std_msgs.msg.Float64),
278 TopicConfig(
'move_base_node/traffic_costmap/inflated_obstacles', nav_msgs.msg.GridCells),
279 TopicConfig(
'move_base_node/traffic_costmap/obstacles', nav_msgs.msg.GridCells),
280 TopicConfig(
'move_base_node/traffic_costmap/parameter_descriptions', dynamic_reconfigure.msg.ConfigDescription),
281 TopicConfig(
'move_base_node/traffic_costmap/parameter_updates', dynamic_reconfigure.msg.Config),
282 TopicConfig(
'move_base_node/traffic_costmap/robot_footprint', geometry_msgs.msg.PolygonStamped),
283 TopicConfig(
'move_base_node/traffic_costmap/unknown_space', nav_msgs.msg.GridCells),
284 TopicConfig(
'move_base_node/visualization_marker', visualization_msgs.msg.Marker),
285 TopicConfig(
'move_base_simple/visualization_marker', visualization_msgs.msg.Marker),
294 TopicConfig(
'robot_state', mir_msgs.msg.RobotState),
301 TopicConfig(
'scan_filter/visualization_marker', visualization_msgs.msg.Marker),
304 TopicConfig(
'/tf', tf2_msgs.msg.TFMessage, dict_filter=_tf_dict_filter),
305 TopicConfig(
'/tf_static', tf2_msgs.msg.TFMessage, dict_filter=_tf_static_dict_filter, latch=
True),
319 TopicConfig(
'cmd_vel', geometry_msgs.msg.Twist, dict_filter=_cmd_vel_dict_filter),
320 TopicConfig(
'initialpose', geometry_msgs.msg.PoseWithCovarianceStamped),
323 TopicConfig(
'move_base/cancel', actionlib_msgs.msg.GoalID),
325 TopicConfig(
'move_base/goal', move_base_msgs.msg.MoveBaseActionGoal, dict_filter=_move_base_goal_dict_filter),
335 self.
pub = rospy.Publisher(
336 name=topic_config.topic,
337 data_class=topic_config.topic_type,
338 subscriber_listener=self,
339 latch=topic_config.latch,
343 "[%s] publishing topic '%s' [%s]", rospy.get_name(), topic_config.topic, topic_config.topic_type._type
346 if topic_config.latch:
352 rospy.loginfo(
"[%s] starting to stream messages on topic '%s'", rospy.get_name(), self.
topic_config.topic)
353 absolute_topic =
'/' + self.
topic_config.topic.lstrip(
'/')
354 self.
robot.subscribe(topic=absolute_topic, callback=self.
callback)
367 if self.topic_config.dict_filter
is not None:
368 msg_dict = self.topic_config.dict_filter(msg_dict)
369 msg = message_converter.convert_dictionary_to_ros_message(self.topic_config.topic_type._type, msg_dict)
370 self.pub.publish(msg)
378 self.
sub = rospy.Subscriber(
379 name=topic_config.topic, data_class=topic_config.topic_type, callback=self.
callback, queue_size=10
382 "[%s] subscribing to topic '%s' [%s]", rospy.get_name(), topic_config.topic, topic_config.topic_type._type
386 msg_dict = message_converter.convert_ros_message_to_dictionary(msg)
390 absolute_topic =
'/' + self.
topic_config.topic.lstrip(
'/')
391 self.
robot.publish(absolute_topic, msg_dict)
397 hostname = rospy.get_param(
'~hostname')
399 rospy.logfatal(
'[%s] parameter "hostname" is not set!', rospy.get_name())
401 port = rospy.get_param(
'~port', 9090)
404 tf_prefix = rospy.get_param(
'~tf_prefix',
'').strip(
'/')
406 rospy.loginfo(
'[%s] trying to connect to %s:%i...', rospy.get_name(), hostname, port)
407 self.
robot = rosbridge.RosbridgeSetup(hostname, port)
411 while not self.
robot.is_connected():
412 if rospy.is_shutdown():
414 if self.
robot.is_errored():
415 rospy.logfatal(
'[%s] connection error to %s:%i, giving up!', rospy.get_name(), hostname, port)
418 rospy.logwarn(
'[%s] still waiting for connection to %s:%i...', rospy.get_name(), hostname, port)
422 rospy.loginfo(
'[%s] ... connected.', rospy.get_name())
425 published_topics = [topic_name
for (topic_name, _, has_publishers, _)
in topics
if has_publishers]
426 subscribed_topics = [topic_name
for (topic_name, _, _, has_subscribers)
in topics
if has_subscribers]
428 for pub_topic
in PUB_TOPICS:
430 absolute_topic =
'/' + pub_topic.topic.lstrip(
'/')
431 if absolute_topic
not in published_topics:
432 rospy.logwarn(
"[%s] topic '%s' is not published by the MiR!", rospy.get_name(), pub_topic.topic)
434 for sub_topic
in SUB_TOPICS:
436 absolute_topic =
'/' + sub_topic.topic.lstrip(
'/')
437 if absolute_topic
not in subscribed_topics:
438 rospy.logwarn(
"[%s] topic '%s' is not yet subscribed to by the MiR!", rospy.get_name(), sub_topic.topic)
446 srv_response = self.
robot.callService(
'/rosapi/topics', msg={})
447 topic_names = sorted(srv_response[
'topics'])
450 for topic_name
in topic_names:
451 srv_response = self.
robot.callService(
"/rosapi/topic_type", msg={
'topic': topic_name})
452 topic_type = srv_response[
'type']
454 srv_response = self.
robot.callService(
"/rosapi/publishers", msg={
'topic': topic_name})
455 has_publishers =
True if len(srv_response[
'publishers']) > 0
else False 457 srv_response = self.
robot.callService(
"/rosapi/subscribers", msg={
'topic': topic_name})
458 has_subscribers =
True if len(srv_response[
'subscribers']) > 0
else False 460 topics.append([topic_name, topic_type, has_publishers, has_subscribers])
463 for (topic_name, topic_type, has_publishers, has_subscribers)
in topics:
465 print ' * %s [%s]' % (topic_name, topic_type)
467 print '\nSubscribers:' 468 for (topic_name, topic_type, has_publishers, has_subscribers)
in topics:
470 print ' * %s [%s]' % (topic_name, topic_type)
476 rospy.logwarn(
"Could not connect to 'move_base' server after two seconds. Dropping goal.")
477 rospy.logwarn(
"Did you activate 'planner' in the MIR web interface?")
479 goal = move_base_msgs.msg.MoveBaseGoal()
480 goal.target_pose.header = copy.deepcopy(msg.header)
481 goal.target_pose.pose = copy.deepcopy(msg.pose)
486 rospy.init_node(
'mir_bridge')
491 if __name__ ==
'__main__':
494 except rospy.ROSInterruptException:
def _tf_static_dict_filter(msg_dict)
def peer_subscribe(self, topic_name, topic_publish, peer_publish)
def _move_base_feedback_dict_filter(msg_dict)
def _move_base_simple_goal_callback(self, msg)
def _cmd_vel_dict_filter(msg_dict)
def __init__(self, topic_config, robot)
def __init__(self, topic_config, robot)
def __init__(self, topic, topic_type, latch=False, dict_filter=None)
def peer_unsubscribe(self, topic_name, num_peers)
def _prepend_tf_prefix_dict_filter(msg_dict)
def _tf_dict_filter(msg_dict)
def _move_base_result_dict_filter(msg_dict)
def _move_base_goal_dict_filter(msg_dict)
def _remove_tf_prefix_dict_filter(msg_dict)
def callback(self, msg_dict)