You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1307 lines
60 KiB

  1. #!/usr/bin/env python
  2. from __future__ import print_function
  3. import rospy
  4. import actionlib
  5. from sensor_msgs.msg import JointState
  6. from geometry_msgs.msg import Transform, TransformStamped, PoseWithCovarianceStamped, PoseStamped, Pose, PointStamped
  7. from nav_msgs.msg import Odometry
  8. from move_base_msgs.msg import MoveBaseAction, MoveBaseResult, MoveBaseFeedback
  9. from nav_msgs.srv import GetPlan
  10. from nav_msgs.msg import Path
  11. from sensor_msgs.msg import PointCloud2
  12. from visualization_msgs.msg import Marker, MarkerArray
  13. from std_srvs.srv import Trigger, TriggerResponse, TriggerRequest
  14. from tf.transformations import euler_from_quaternion
  15. from tf2_geometry_msgs import do_transform_pose
  16. import numpy as np
  17. import scipy.ndimage as nd
  18. import cv2
  19. import math
  20. import time
  21. import threading
  22. import sys
  23. import os
  24. import copy
  25. import tf_conversions
  26. import ros_numpy
  27. import tf2_ros
  28. import argparse as ap
  29. import hello_helpers.hello_misc as hm
  30. import hello_helpers.hello_ros_viz as hr
  31. import stretch_funmap.merge_maps as mm
  32. import stretch_funmap.navigate as nv
  33. import stretch_funmap.mapping as ma
  34. import stretch_funmap.segment_max_height_image as sm
  35. import stretch_funmap.navigation_planning as na
  36. import stretch_funmap.manipulation_planning as mp
  37. def create_map_to_odom_transform(t_mat):
  38. t = TransformStamped()
  39. t.header.stamp = rospy.Time.now()
  40. t.header.frame_id = 'map'
  41. t.child_frame_id = 'odom'
  42. t.transform = ros_numpy.msgify(Transform, t_mat)
  43. return t
  44. class ContactDetector():
  45. def __init__(self, get_joint_state, in_contact_func, move_increment=0.008):
  46. self.in_contact_func = in_contact_func
  47. # reach until contact related
  48. self.in_contact = False
  49. self.in_contact_position = None
  50. self.contact_state_lock = threading.Lock()
  51. self.contact_mode = None
  52. self.contact_mode_lock = threading.Lock()
  53. self.position = None
  54. self.av_effort = None
  55. self.av_effort_window_size = 3
  56. self.get_joint_state = get_joint_state
  57. self.direction_sign = None
  58. self.stopping_position = None
  59. self.min_av_effort_threshold = 10.0
  60. self.move_increment = move_increment
  61. def set_regulate_contact(self):
  62. with self.contact_mode_lock:
  63. return self.contact_mode == 'regulate_contact'
  64. def set_stopping_position(self, stopping_position, direction_sign):
  65. assert((direction_sign == -1) or (direction_sign == 1))
  66. self.stopping_position = stopping_position
  67. self.direction_sign = direction_sign
  68. def is_in_contact(self):
  69. with self.contact_state_lock:
  70. return self.in_contact
  71. def contact_position(self):
  72. with self.contact_state_lock:
  73. return self.in_contact_position
  74. def get_position(self):
  75. return self.position
  76. def passed_stopping_position(self):
  77. if (self.position is None) or (self.stopping_position is None):
  78. return False
  79. difference = self.stopping_position - self.position
  80. if int(np.sign(difference)) == self.direction_sign:
  81. return False
  82. return True
  83. def not_stopped(self):
  84. with self.contact_mode_lock:
  85. return self.contact_mode == 'stop_on_contact'
  86. def reset(self):
  87. with self.contact_state_lock:
  88. self.in_contact = False
  89. self.in_contact_position = None
  90. self.turn_off()
  91. self.stopping_position = None
  92. self.direction_sign = None
  93. def turn_off(self):
  94. with self.contact_mode_lock:
  95. self.contact_mode = None
  96. def turn_on(self):
  97. with self.contact_mode_lock:
  98. self.contact_mode = 'stop_on_contact'
  99. def update(self, joint_states, stop_the_robot_service):
  100. with self.contact_state_lock:
  101. self.in_contact = False
  102. self.in_contact_wrist_position = None
  103. position, velocity, effort = self.get_joint_state(joint_states)
  104. self.position = position
  105. # First, check that the stopping position, if defined, has not been passed
  106. if self.passed_stopping_position():
  107. trigger_request = TriggerRequest()
  108. trigger_result = stop_the_robot_service(trigger_request)
  109. with self.contact_mode_lock:
  110. self.contact_mode = 'passed_stopping_point'
  111. rospy.loginfo('stop_on_contact: stopping the robot due to passing the stopping position, position = {0}, stopping_position = {1}, direction_sign = {2}'.format(self.position, self.stopping_position, self.direction_sign))
  112. # Second, check that the effort thresholds have not been exceeded
  113. if self.av_effort is None:
  114. self.av_effort = effort
  115. else:
  116. self.av_effort = (((self.av_effort_window_size - 1.0) * self.av_effort) + effort) / self.av_effort_window_size
  117. if self.in_contact_func(effort, self.av_effort):
  118. # Contact detected!
  119. with self.contact_state_lock:
  120. self.in_contact = True
  121. self.in_contact_position = self.position
  122. with self.contact_mode_lock:
  123. if self.contact_mode == 'stop_on_contact':
  124. trigger_request = TriggerRequest()
  125. trigger_result = stop_the_robot_service(trigger_request)
  126. rospy.loginfo('stop_on_contact: stopping the robot due to detected contact, effort = {0}, av_effort = {1}'.format(effort, self.av_effort))
  127. self.contact_mode = 'regulate_contact'
  128. elif self.contact_mode == 'regulate_contact':
  129. pass
  130. elif self.av_effort < self.min_av_effort_threshold:
  131. with self.contact_mode_lock:
  132. if self.contact_mode == 'regulate_contact':
  133. pass
  134. else:
  135. pass
  136. def move_until_contact(self, joint_name, stopping_position, direction_sign, move_to_pose):
  137. self.reset()
  138. self.set_stopping_position(stopping_position, direction_sign)
  139. success = False
  140. message = 'Unknown result.'
  141. if not self.passed_stopping_position():
  142. # The target has not been passed
  143. self.turn_on()
  144. move_rate = rospy.Rate(5.0)
  145. move_increment = direction_sign * self.move_increment
  146. finished = False
  147. while self.not_stopped():
  148. position = self.get_position()
  149. if position is not None:
  150. new_target = self.get_position() + move_increment
  151. pose = {joint_name : new_target}
  152. move_to_pose(pose, async=True)
  153. move_rate.sleep()
  154. if self.is_in_contact():
  155. # back off from the detected contact location
  156. contact_position = self.contact_position()
  157. if contact_position is not None:
  158. new_target = contact_position - 0.001 #- 0.002
  159. else:
  160. new_target = self.position() - 0.001 #- 0.002
  161. pose = {joint_name : new_target}
  162. move_to_pose(pose, async=False)
  163. rospy.loginfo('backing off after contact: moving away from surface to decrease force')
  164. success = True
  165. message = 'Successfully reached until contact.'
  166. else:
  167. success = False
  168. message = 'Terminated without detecting contact.'
  169. self.reset()
  170. return success, message
  171. class FunmapNode(hm.HelloNode):
  172. def __init__(self, map_filename):
  173. hm.HelloNode.__init__(self)
  174. self.map_filename = map_filename
  175. self.debug_directory = None
  176. # This holds all the poses the robot's mobile base was in
  177. # while making scans merged into the map. They are defined
  178. # with respect to the map's image. One use of this list is to
  179. # fill in the robot's footprints as floor when producing a
  180. # floor mask for the purposes of navigations with the
  181. # assumption that the robot's base will only be on traversable
  182. # floor.
  183. self.robot_poses = []
  184. self.prev_nav_markers = None
  185. self.wrist_position = None
  186. self.use_hook = False #True #False
  187. if self.use_hook:
  188. def extension_contact_func(effort, av_effort):
  189. single_effort_threshold = 38.0
  190. av_effort_threshold = 34.0
  191. if (effort >= single_effort_threshold):
  192. rospy.loginfo('Extension single effort exceeded single_effort_threshold: {0} >= {1}'.format(effort, single_effort_threshold))
  193. if (av_effort >= av_effort_threshold):
  194. rospy.loginfo('Extension average effort exceeded av_effort_threshold: {0} >= {1}'.format(av_effort, av_effort_threshold))
  195. return ((effort >= single_effort_threshold) or
  196. (av_effort > av_effort_threshold))
  197. self.extension_contact_detector = ContactDetector(hm.get_wrist_state, extension_contact_func, move_increment=0.008)
  198. else:
  199. def extension_contact_func(effort, av_effort):
  200. single_effort_threshold = 40.0
  201. av_effort_threshold = 40.0
  202. if (effort >= single_effort_threshold):
  203. rospy.loginfo('Extension single effort exceeded single_effort_threshold: {0} >= {1}'.format(effort, single_effort_threshold))
  204. if (av_effort >= av_effort_threshold):
  205. rospy.loginfo('Extension average effort exceeded av_effort_threshold: {0} >= {1}'.format(av_effort, av_effort_threshold))
  206. return ((effort >= single_effort_threshold) or
  207. (av_effort > av_effort_threshold))
  208. self.extension_contact_detector = ContactDetector(hm.get_wrist_state, extension_contact_func)
  209. def lift_contact_func(effort, av_effort):
  210. single_effort_threshold = 20.0
  211. av_effort_threshold = 20.0
  212. if (effort <= single_effort_threshold):
  213. rospy.loginfo('Lift single effort less than single_effort_threshold: {0} <= {1}'.format(effort, single_effort_threshold))
  214. if (av_effort <= av_effort_threshold):
  215. rospy.loginfo('Lift average effort less than av_effort_threshold: {0} <= {1}'.format(av_effort, av_effort_threshold))
  216. return ((effort <= single_effort_threshold) or
  217. (av_effort < av_effort_threshold))
  218. self.lift_down_contact_detector = ContactDetector(hm.get_lift_state, lift_contact_func)
  219. def publish_map_point_cloud(self):
  220. if self.merged_map is not None:
  221. max_height_point_cloud = self.merged_map.max_height_im.to_point_cloud()
  222. self.point_cloud_pub.publish(max_height_point_cloud)
  223. pub_voi = True
  224. if pub_voi:
  225. marker = self.merged_map.max_height_im.voi.get_ros_marker(duration=1000.0)
  226. self.voi_marker_pub.publish(marker)
  227. def publish_nav_plan_markers(self, line_segment_path, image_to_points_mat, clicked_frame_id):
  228. path_height_m = 0.2
  229. points = [np.matmul(image_to_points_mat, np.array([p[0], p[1], path_height_m, 1.0]))[:3] for p in line_segment_path]
  230. points = [[p[0], p[1], path_height_m] for p in points]
  231. self.publish_path_markers(points, clicked_frame_id)
  232. def publish_path_markers(self, points, points_frame_id):
  233. path_height_m = 0.2
  234. points = [[p[0], p[1], path_height_m] for p in points]
  235. if self.prev_nav_markers is not None:
  236. # delete previous markers
  237. for m in self.prev_nav_markers.markers:
  238. m.action = m.DELETE
  239. self.navigation_plan_markers_pub.publish(self.prev_nav_markers)
  240. nav_markers = MarkerArray()
  241. duration_s = 1 * 60
  242. timestamp = rospy.Time.now()
  243. m = hr.create_line_strip(points, 0, points_frame_id, timestamp, rgba=[0.0, 1.0, 0.0, 1.0], line_width_m=0.05, duration_s=duration_s)
  244. nav_markers.markers.append(m)
  245. for i, p in enumerate(points):
  246. m = hr.create_sphere_marker(p, i+1, points_frame_id, timestamp, rgba=[1.0, 1.0, 1.0, 1.0], diameter_m=0.15, duration_s=duration_s)
  247. nav_markers.markers.append(m)
  248. self.navigation_plan_markers_pub.publish(nav_markers)
  249. self.prev_nav_markers = nav_markers
  250. def trigger_align_with_nearest_cliff_service_callback(self, request):
  251. manip = mp.ManipulationView(self.tf2_buffer, self.debug_directory)
  252. manip.move_head(self.move_to_pose)
  253. manip.update(self.point_cloud, self.tf2_buffer)
  254. if self.debug_directory is not None:
  255. dirname = self.debug_directory + 'align_with_nearest_cliff/'
  256. # If the directory does not already exist, create it.
  257. if not os.path.exists(dirname):
  258. os.makedirs(dirname)
  259. filename = 'nearest_cliff_scan_' + hm.create_time_string()
  260. manip.save_scan(dirname + filename)
  261. else:
  262. rospy.loginfo('FunmapNode trigger_align_with_nearest_cliff_service_callback: No debug directory provided, so debugging data will not be saved.')
  263. p0, p1, normal = manip.get_nearest_cliff('odom', self.tf2_buffer)
  264. if normal is not None:
  265. cliff_ang = np.arctan2(normal[1], normal[0])
  266. # Find the robot's current pose in the odom frame.
  267. xya, timestamp = self.get_robot_floor_pose_xya(floor_frame='odom')
  268. robot_ang = xya[2]
  269. align_arm_ang = robot_ang + (np.pi/2.0)
  270. # Find the angle that the robot should turn in order
  271. # to point toward the next waypoint.
  272. turn_ang = hm.angle_diff_rad(cliff_ang, align_arm_ang)
  273. # Command the robot to turn to point to the next
  274. # waypoint.
  275. at_goal = self.move_base.turn(turn_ang, publish_visualizations=True)
  276. if not at_goal:
  277. message_text = 'Failed to reach turn goal.'
  278. rospy.loginfo(message_text)
  279. success=False
  280. message=message_text
  281. else:
  282. success = True
  283. message = 'Aligned with the nearest edge.'
  284. else:
  285. success = False
  286. message = 'Failed to detect cliff.'
  287. return TriggerResponse(
  288. success=success,
  289. message=message
  290. )
  291. def joint_states_callback(self, joint_states):
  292. self.extension_contact_detector.update(joint_states, self.stop_the_robot_service)
  293. self.wrist_position = self.extension_contact_detector.get_position()
  294. self.lift_down_contact_detector.update(joint_states, self.stop_the_robot_service)
  295. self.lift_position = self.lift_down_contact_detector.get_position()
  296. def trigger_reach_until_contact_service_callback(self, request):
  297. manip = mp.ManipulationView(self.tf2_buffer, self.debug_directory)
  298. manip.move_head(self.move_to_pose)
  299. manip.update(self.point_cloud, self.tf2_buffer)
  300. if self.debug_directory is not None:
  301. dirname = self.debug_directory + 'reach_until_contact/'
  302. # If the directory does not already exist, create it.
  303. if not os.path.exists(dirname):
  304. os.makedirs(dirname)
  305. filename = 'reach_until_contact_' + hm.create_time_string()
  306. manip.save_scan(dirname + filename)
  307. else:
  308. rospy.loginfo('FunmapNode trigger_reach_until_contact_service_callback: No debug directory provided, so debugging data will not be saved.')
  309. if self.use_hook:
  310. tooltip_frame = 'link_hook'
  311. else:
  312. tooltip_frame = 'link_grasp_center'
  313. reach_m = manip.estimate_reach_to_contact_distance(tooltip_frame, self.tf2_buffer)
  314. rospy.loginfo('----------------')
  315. rospy.loginfo('reach_m = {0}'.format(reach_m))
  316. rospy.loginfo('----------------')
  317. # Be aggressive moving in observed freespace and cautious
  318. # moving toward a perceived obstacle or unknown region.
  319. success = False
  320. message = 'Unknown result.'
  321. if self.wrist_position is not None:
  322. # The current wrist position needs to be known in order
  323. # for a reach command to be sent.
  324. max_reach_target_m = 0.5
  325. if (reach_m is not None):
  326. reach_target_m = reach_m + self.wrist_position
  327. else:
  328. reach_target_m = None
  329. if (reach_target_m is None) or (reach_target_m > max_reach_target_m):
  330. # Either the observed reach target was too far for the
  331. # arm, in which case we assume that something strange
  332. # happened and reach cautiously over the full reach.
  333. # Or, a freespace reach was not observed, so reach
  334. # cautiously over the full reach.
  335. direction_sign = 1
  336. success, message = self.extension_contact_detector.move_until_contact('wrist_extension', max_reach_target_m, direction_sign, self.move_to_pose)
  337. else:
  338. # A freespace region was observed. Agressively move to
  339. # within a safe distance of the expected obstacle.
  340. safety_margin_m = 0.02
  341. safe_target_m = reach_target_m - safety_margin_m
  342. if self.use_hook:
  343. safe_target_m = safe_target_m + 0.03
  344. if safe_target_m > self.wrist_position:
  345. pose = {'wrist_extension' : safe_target_m}
  346. self.move_to_pose(pose, async=False)
  347. # target depth within the surface
  348. target_depth_m = 0.08
  349. in_contact_target_m = reach_target_m + target_depth_m
  350. direction_sign = 1
  351. success, message = self.extension_contact_detector.move_until_contact('wrist_extension', in_contact_target_m, direction_sign, self.move_to_pose)
  352. return TriggerResponse(
  353. success=success,
  354. message=message
  355. )
  356. def trigger_lower_until_contact_service_callback(self, request):
  357. direction_sign = -1
  358. lowest_allowed_m = 0.3
  359. success, message = self.lift_down_contact_detector.move_until_contact('joint_lift', lowest_allowed_m, direction_sign, self.move_to_pose)
  360. return TriggerResponse(
  361. success=success,
  362. message=message
  363. )
  364. def trigger_global_localization_service_callback(self, request):
  365. self.perform_head_scan(localize_only=True, global_localization=True)
  366. return TriggerResponse(
  367. success=True,
  368. message='Completed localization with scan.'
  369. )
  370. def trigger_local_localization_service_callback(self, request):
  371. self.perform_head_scan(localize_only=True, global_localization=False, fast_scan=True)
  372. return TriggerResponse(
  373. success=True,
  374. message='Completed localization with scan.'
  375. )
  376. def trigger_head_scan_service_callback(self, request):
  377. self.perform_head_scan()
  378. return TriggerResponse(
  379. success=True,
  380. message='Completed head scan.'
  381. )
  382. def trigger_drive_to_scan_service_callback(self, request):
  383. if self.merged_map is None:
  384. return TriggerResponse(
  385. success=False,
  386. message='No map exists yet, so unable to drive to a good scan spot.'
  387. )
  388. max_height_im = self.merged_map.max_height_im
  389. robot_xy_pix, robot_ang_rad, timestamp = max_height_im.get_robot_pose_in_image(self.tf2_buffer)
  390. robot_xya_pix = [robot_xy_pix[0], robot_xy_pix[1], robot_ang_rad]
  391. robot_x_pix = int(round(robot_xy_pix[0]))
  392. robot_y_pix = int(round(robot_xy_pix[1]))
  393. # Define the target maximum observation distance for any
  394. # observed point in the map. This serves as a goal for mapping.
  395. max_scan_distance_m = 1.5
  396. # The best case minimum width of the robot in meters when moving forward and backward.
  397. min_robot_width_m = 0.34
  398. camera_height_m = 1.12
  399. floor_mask = sm.compute_floor_mask(max_height_im)
  400. # Select the next location on the map from which to
  401. # attempt to make a head scan.
  402. best_xy = na.select_next_scan_location(floor_mask, max_height_im, min_robot_width_m,
  403. robot_x_pix, robot_y_pix, robot_ang_rad,
  404. camera_height_m, max_scan_distance_m,
  405. display_on=False)
  406. if best_xy is None:
  407. return TriggerResponse(
  408. success=False,
  409. message='No good scan location was detected.'
  410. )
  411. # Plan an optimistic path on the floor to the next
  412. # location for scanning.
  413. end_xy = np.array(best_xy)
  414. success, message = self.navigate_to_map_pixel(end_xy, robot_xya_pix=robot_xya_pix, floor_mask=floor_mask)
  415. return TriggerResponse(
  416. success=success,
  417. message=message
  418. )
  419. def pose_to_map_pixel(self, pose_stamped):
  420. clicked_frame_id = pose_stamped.header.frame_id
  421. clicked_timestamp = pose_stamped.header.stamp
  422. clicked_point = pose_stamped.pose.position
  423. # Check if a map exists
  424. if self.merged_map is None:
  425. success = False
  426. message = 'No map exists yet, so unable to drive to a good scan spot.'
  427. rospy.logerr(message)
  428. return None
  429. max_height_im = self.merged_map.max_height_im
  430. map_frame_id = self.merged_map.max_height_im.voi.frame_id
  431. points_to_image_mat, pi_timestamp = max_height_im.get_points_to_image_mat(clicked_frame_id, self.tf2_buffer)
  432. #lookup_time=clicked_timestamp)
  433. if (points_to_image_mat is not None):
  434. c_x = clicked_point.x
  435. c_y = clicked_point.y
  436. c_z = clicked_point.z
  437. clicked_xyz = np.array([c_x, c_y, c_z, 1.0])
  438. clicked_image_pixel = np.matmul(points_to_image_mat, clicked_xyz)
  439. i_x, i_y, i_z = clicked_image_pixel[:3]
  440. rospy.loginfo('clicked_image_pixel =' + str(clicked_image_pixel))
  441. end_xy = np.int64(np.round(np.array([i_x, i_y])))
  442. rospy.loginfo('end_xy =' + str(end_xy))
  443. return end_xy
  444. return None
  445. def plan_a_path(self, end_xy_pix, robot_xya_pix=None, floor_mask=None):
  446. # Transform the robot's current estimated pose as represented
  447. # by TF2 to the map image. Currently, the estimated pose is
  448. # based on the transformation from the map frame to the
  449. # base_link frame, which is updated by odometry and
  450. # corrections based on matching head scans to the map.
  451. path = None
  452. # Check if a map exists
  453. if self.merged_map is None:
  454. message = 'No map exists yet, so unable to drive to a good scan spot.'
  455. return path, message
  456. max_height_im = self.merged_map.max_height_im
  457. if robot_xya_pix is None:
  458. robot_xy_pix, robot_ang_rad, timestamp = max_height_im.get_robot_pose_in_image(self.tf2_buffer)
  459. robot_xya_pix = [robot_xy_pix[0], robot_xy_pix[1], robot_ang_rad]
  460. max_height_im = self.merged_map.max_height_im
  461. line_segment_path, message = na.plan_a_path(max_height_im, robot_xya_pix,
  462. end_xy_pix, floor_mask=floor_mask)
  463. return line_segment_path, message
  464. def plan_to_reach(self, reach_xyz_pix, robot_xya_pix=None, floor_mask=None):
  465. # This is intended to perform coarse positioning of the
  466. # gripper near a target 3D point.
  467. robot_reach_xya_pix = None
  468. wrist_extension_m = None
  469. i_x, i_y, i_z = reach_xyz_pix
  470. max_height_im = self.merged_map.max_height_im
  471. # Check if a map exists
  472. if self.merged_map is None:
  473. message = 'No map exists yet, so unable to plan a reach.'
  474. rospy.logerr(message)
  475. return None, None
  476. if robot_xya_pix is None:
  477. robot_xy_pix, robot_ang_rad, timestamp = max_height_im.get_robot_pose_in_image(self.tf2_buffer)
  478. robot_xya_pix = [robot_xy_pix[0], robot_xy_pix[1], robot_ang_rad]
  479. end_xy_pix = np.int64(np.round(np.array([i_x, i_y])))
  480. m_per_height_unit = max_height_im.m_per_height_unit
  481. # move the gripper to be above the target point
  482. extra_target_height_m = 0.01
  483. target_z = i_z + (extra_target_height_m / m_per_height_unit)
  484. target_z_m = target_z * m_per_height_unit
  485. target_xyz_pix = (end_xy_pix[0], end_xy_pix[1], target_z)
  486. image_display_on = False
  487. manipulation_planner = mp.ManipulationPlanner()
  488. base_x_pix, base_y_pix, base_ang_rad, wrist_extension_m = manipulation_planner.base_pose(max_height_im,
  489. target_xyz_pix,
  490. robot_xya_pix,
  491. image_display_on=image_display_on)
  492. if image_display_on:
  493. c = cv2.waitKey(0)
  494. if base_x_pix is None:
  495. rospy.logerr('No valid base pose found for reaching the target.')
  496. return None, None
  497. robot_reach_xya_pix = [base_x_pix, base_y_pix, base_ang_rad]
  498. base_link_point = max_height_im.get_pix_in_frame(np.array(reach_xyz_pix), 'base_link', self.tf2_buffer)
  499. simple_reach_plan = []
  500. # close the gripper
  501. simple_reach_plan.append({'joint_gripper_finger_left': 0.0})
  502. # move the lift to be at the height of the target
  503. # The fingers of the gripper touch the floor at a joint_lift
  504. # height of 0.0 m, so moving the lift link to the height of
  505. # the target will result in the fingers being at the height of
  506. # the target.
  507. height_m = base_link_point[2]
  508. safety_z_m = 0.0
  509. simple_reach_plan.append({'joint_lift': height_m + safety_z_m})
  510. # rotate the gripper to be in the center
  511. # of the swept volume of the wrist (a
  512. # little right of center when looking out
  513. # from the robot to the gripper)
  514. #simple_reach_plan.append({'joint_gripper': -0.25})
  515. simple_reach_plan.append({'joint_wrist_yaw': -0.25})
  516. # reach out to the target
  517. # Reach to a point that is not fully at the target.
  518. safety_reach_m = 0.1 # 10cm away from the target
  519. simple_reach_plan.append({'wrist_extension': wrist_extension_m - safety_reach_m})
  520. return robot_reach_xya_pix, simple_reach_plan
  521. def reach_to_click_callback(self, clicked_msg):
  522. rospy.loginfo('clicked_msg =' + str(clicked_msg))
  523. clicked_frame_id = clicked_msg.header.frame_id
  524. clicked_timestamp = clicked_msg.header.stamp
  525. clicked_point = clicked_msg.point
  526. max_height_im = self.merged_map.max_height_im
  527. # Check if a map exists
  528. if self.merged_map is None:
  529. message = 'No map exists yet, so unable to plan a reach.'
  530. rospy.logerr(message)
  531. return
  532. points_to_image_mat, pi_timestamp = max_height_im.get_points_to_image_mat(clicked_frame_id, self.tf2_buffer)
  533. if points_to_image_mat is None:
  534. rospy.logerr('points_to_image_mat not found')
  535. return
  536. c_x = clicked_point.x
  537. c_y = clicked_point.y
  538. c_z = clicked_point.z
  539. clicked_xyz = np.array([c_x, c_y, c_z, 1.0])
  540. clicked_image_pixel = np.matmul(points_to_image_mat, clicked_xyz)[:3]
  541. i_x, i_y, i_z = clicked_image_pixel
  542. rospy.loginfo('clicked_image_pixel =' + str(clicked_image_pixel))
  543. h, w = max_height_im.image.shape
  544. if not ((i_x >= 0) and (i_y >= 0) and (i_x < w) and (i_y < h)):
  545. rospy.logerr('clicked point does not fall within the bounds of the max_height_image')
  546. return
  547. robot_xy_pix, robot_ang_rad, timestamp = max_height_im.get_robot_pose_in_image(self.tf2_buffer)
  548. robot_xya_pix = [robot_xy_pix[0], robot_xy_pix[1], robot_ang_rad]
  549. reach_xyz_pix = clicked_image_pixel
  550. robot_reach_xya_pix, simple_reach_plan = self.plan_to_reach(reach_xyz_pix, robot_xya_pix=robot_xya_pix)
  551. success, message = self.navigate_to_map_pixel(robot_reach_xya_pix[:2],
  552. end_angle=robot_reach_xya_pix[2],
  553. robot_xya_pix=robot_xya_pix)
  554. if success:
  555. for pose in simple_reach_plan:
  556. self.move_to_pose(pose)
  557. else:
  558. rospy.logerr(message)
  559. rospy.logerr('Aborting reach attempt due to failed navigation')
  560. return
  561. def navigate_to_map_pixel(self, end_xy, end_angle=None, robot_xya_pix=None, floor_mask=None):
  562. # Set the D435i to Default mode for obstacle detection
  563. trigger_request = TriggerRequest()
  564. trigger_result = self.trigger_d435i_default_mode_service(trigger_request)
  565. rospy.loginfo('trigger_result = {0}'.format(trigger_result))
  566. # Move the head to a pose from which the D435i can detect
  567. # obstacles near the front of the mobile base while moving
  568. # forward.
  569. self.move_base.head_to_forward_motion_pose()
  570. line_segment_path, message = self.plan_a_path(end_xy, robot_xya_pix=robot_xya_pix, floor_mask=floor_mask)
  571. if line_segment_path is None:
  572. success = False
  573. return success, message
  574. # Existence of the merged map is checked by plan_a_path, but
  575. # to avoid future issues I'm introducing this redundancy.
  576. if self.merged_map is None:
  577. success = False
  578. return success, 'No map available for planning and navigation.'
  579. max_height_im = self.merged_map.max_height_im
  580. map_frame_id = self.merged_map.max_height_im.voi.frame_id
  581. # Query TF2 to obtain the current estimated transformation
  582. # from the map image to the map frame.
  583. image_to_points_mat, ip_timestamp = max_height_im.get_image_to_points_mat(map_frame_id, self.tf2_buffer)
  584. if image_to_points_mat is not None:
  585. # Publish a marker array to visualize the line segment path.
  586. self.publish_nav_plan_markers(line_segment_path, image_to_points_mat, map_frame_id)
  587. # Iterate through the vertices of the line segment path,
  588. # commanding the robot to drive to them in sequence using
  589. # in place rotations and forward motions.
  590. successful = True
  591. for p0, p1 in zip(line_segment_path, line_segment_path[1:]):
  592. # Query TF2 to obtain the current estimated transformation
  593. # from the image to the odometry frame.
  594. image_to_odom_mat, io_timestamp = max_height_im.get_image_to_points_mat('odom', self.tf2_buffer)
  595. # Query TF2 to obtain the current estimated transformation
  596. # from the robot's base_link frame to the odometry frame.
  597. robot_to_odom_mat, ro_timestamp = hm.get_p1_to_p2_matrix('base_link', 'odom', self.tf2_buffer)
  598. # Navigation planning is performed with respect to a
  599. # odom frame height of 0.0, so the heights of
  600. # transformed points are 0.0. The simple method of
  601. # handling the heights below assumes that the odom
  602. # frame is aligned with the floor, so that ignoring
  603. # the z coordinate is approximately equivalent to
  604. # projecting a point onto the floor.
  605. # Convert the current and next waypoints from map
  606. # image pixel coordinates to the odom
  607. # frame.
  608. p0 = np.array([p0[0], p0[1], 0.0, 1.0])
  609. p0 = np.matmul(image_to_odom_mat, p0)[:2]
  610. p1 = np.array([p1[0], p1[1], 0.0, 1.0])
  611. next_point_xyz = np.matmul(image_to_odom_mat, p1)
  612. p1 = next_point_xyz[:2]
  613. # Find the robot's current pose in the odom frame.
  614. xya, timestamp = self.get_robot_floor_pose_xya()
  615. r0 = xya[:2]
  616. r_ang = xya[2]
  617. # Check how far the robot's current location is from
  618. # its current waypoint. The current waypoint is where
  619. # the robot would ideally be located.
  620. waypoint_tolerance_m = 0.25
  621. waypoint_error = np.linalg.norm(p0 - r0)
  622. rospy.loginfo('waypoint_error =' + str(waypoint_error))
  623. if waypoint_error > waypoint_tolerance_m:
  624. message_text = 'Failed due to waypoint_error being above the maximum allowed error.'
  625. rospy.loginfo(message_text)
  626. success=False
  627. message=message_text
  628. return success, message
  629. # Find the angle in the odometry frame that would
  630. # result in the robot pointing at the next waypoint.
  631. travel_vector = p1 - r0
  632. travel_dist = np.linalg.norm(travel_vector)
  633. travel_ang = np.arctan2(travel_vector[1], travel_vector[0])
  634. rospy.loginfo('travel_dist =' + str(travel_dist))
  635. rospy.loginfo('travel_ang =' + str(travel_ang * (180.0/np.pi)))
  636. # Find the angle that the robot should turn in order
  637. # to point toward the next waypoint.
  638. turn_ang = hm.angle_diff_rad(travel_ang, r_ang)
  639. # Command the robot to turn to point to the next
  640. # waypoint.
  641. rospy.loginfo('robot turn angle in degrees =' + str(turn_ang * (180.0/np.pi)))
  642. at_goal = self.move_base.turn(turn_ang, publish_visualizations=True)
  643. if not at_goal:
  644. message_text = 'Failed to reach turn goal.'
  645. rospy.loginfo(message_text)
  646. success=False
  647. message=message_text
  648. return success, message
  649. # The head seems to drift sometimes over time, such
  650. # that the obstacle detection region is no longer
  651. # observed resulting in false positives. Hopefully,
  652. # this will correct the situation.
  653. self.move_base.head_to_forward_motion_pose()
  654. # FOR FUTURE DEVELOPMENT OF LOCAL NAVIGATION
  655. testing_future_code = False
  656. if testing_future_code:
  657. check_result = self.move_base.check_line_path(next_point_xyz, 'odom')
  658. rospy.loginfo('Result of check line path = {0}'.format(check_result))
  659. local_path, local_path_frame_id = self.move_base.local_plan(next_point_xyz, 'odom')
  660. if local_path is not None:
  661. rospy.loginfo('Found local path! Publishing markers for it!')
  662. self.publish_path_markers(local_path, local_path_frame_id)
  663. else:
  664. rospy.loginfo('Did not find a local path...')
  665. # Command the robot to move forward to the next waypoing.
  666. at_goal = self.move_base.forward(travel_dist, publish_visualizations=True)
  667. if not at_goal:
  668. message_text = 'Failed to reach forward motion goal.'
  669. rospy.loginfo(message_text)
  670. success=False
  671. message=message_text
  672. return success, message
  673. rospy.loginfo('Turn and forward motion succeeded.')
  674. if end_angle is not None:
  675. # If a final target angle has been provided, rotate
  676. # the robot to match the target angle.
  677. rospy.loginfo('Attempting to achieve the final target orientation.')
  678. # Find the robot's current pose in the map frame. This
  679. # assumes that the target angle has been specified
  680. # with respect to the map frame.
  681. xya, timestamp = self.get_robot_floor_pose_xya(floor_frame='map')
  682. r_ang = xya[2]
  683. # Find the angle that the robot should turn in order
  684. # to point toward the next waypoint.
  685. turn_ang = hm.angle_diff_rad(end_angle, r_ang)
  686. # Command the robot to turn to point to the next
  687. # waypoint.
  688. rospy.loginfo('robot turn angle in degrees =' + str(turn_ang * (180.0/np.pi)))
  689. at_goal = self.move_base.turn(turn_ang, publish_visualizations=True)
  690. if not at_goal:
  691. message_text = 'Failed to reach turn goal.'
  692. rospy.loginfo(message_text)
  693. success=False
  694. message=message_text
  695. return success, message
  696. success=True
  697. message='Completed drive to new scan location.'
  698. return success, message
  699. def perform_head_scan(self, fill_in_blindspot_with_second_scan=True, localize_only=False, global_localization=False, fast_scan=False):
  700. node = self
  701. trigger_request = TriggerRequest()
  702. trigger_result = self.trigger_d435i_high_accuracy_mode_service(trigger_request)
  703. rospy.loginfo('trigger_result = {0}'.format(trigger_result))
  704. # Reduce the occlusion due to the arm and grabber. This is
  705. # intended to be run when the standard grabber is not holding
  706. # an object.
  707. ma.stow_and_lower_arm(node)
  708. # Create and perform a new full scan of the environment using
  709. # the head.
  710. head_scan = ma.HeadScan(voi_side_m=16.0)
  711. head_scan.execute_full(node, fast_scan=fast_scan)
  712. scaled_scan = None
  713. scaled_merged_map = None
  714. # Save the new head scan to disk.
  715. if self.debug_directory is not None:
  716. dirname = self.debug_directory + 'head_scans/'
  717. # If the directory does not already exist, create it.
  718. if not os.path.exists(dirname):
  719. os.makedirs(dirname)
  720. filename = 'head_scan_' + hm.create_time_string()
  721. head_scan.save(dirname + filename)
  722. else:
  723. rospy.loginfo('FunmapNode perform_head_scan: No debug directory provided, so debugging data will not be saved.')
  724. head_scan.make_robot_footprint_unobserved()
  725. save_merged_map = False
  726. if self.merged_map is None:
  727. # The robot does not currently have a map, so initialize
  728. # the map with the new head scan.
  729. rospy.loginfo('perform_head_scan: No map available, so setting the map to be the scan that was just taken.')
  730. self.merged_map = head_scan
  731. robot_pose = [head_scan.robot_xy_pix[0], head_scan.robot_xy_pix[1], head_scan.robot_ang_rad]
  732. self.robot_poses.append(robot_pose)
  733. self.localized = True
  734. save_merged_map = True
  735. else:
  736. if localize_only and (not global_localization):
  737. # The scan was performed to localize the robot locally.
  738. rospy.loginfo('perform_head_scan: Performing local localization.')
  739. use_full_size_scans = False
  740. if use_full_size_scans:
  741. affine_matrix, original_robot_map_pose, corrected_robot_map_pose = mm.estimate_scan_1_to_scan_2_transform(head_scan,
  742. self.merged_map,
  743. display_on=False,
  744. show_unaligned=False,
  745. full_localization=False,
  746. init_target=None,
  747. grid_search=False,
  748. small_search=False)
  749. else:
  750. original_robot_map_frame_pose, corrected_robot_map_frame_pose, original_robot_map_image_pose, corrected_robot_map_image_pose, scaled_scan, scaled_merged_map = ma.localize_with_reduced_images(head_scan, self.merged_map, global_localization=False, divisor=2, small_search=True)
  751. corrected_robot_map_pose = corrected_robot_map_frame_pose
  752. original_robot_map_pose = original_robot_map_frame_pose
  753. # Save the scaled scans to disk for debugging.
  754. if self.debug_directory is not None:
  755. dirname = self.debug_directory + 'scaled_localization_scans/'
  756. # If the directory does not already exist, create it.
  757. if not os.path.exists(dirname):
  758. os.makedirs(dirname)
  759. time_string = hm.create_time_string()
  760. filename = 'localization_scaled_head_scan_' + time_string
  761. scaled_scan.save(dirname + filename)
  762. filename = 'localization_scaled_merged_map_' + time_string
  763. scaled_merged_map.save(dirname + filename)
  764. else:
  765. rospy.loginfo('FunmapNode perform_head_scan: No debug directory provided, so debugging data will not be saved.')
  766. self.localized = True
  767. elif (not self.localized) or (localize_only and global_localization):
  768. # The robot has not been localized with respect to the
  769. # current map or the scan was performed solely to
  770. # globally localize the robot. This attempts to
  771. # localize the robot on the map by reducing the sizes
  772. # of the scan and the map in order to more efficiently
  773. # search for a match globally.
  774. # This does not merge the new scan into the current map.
  775. rospy.loginfo('perform_head_scan: Performing global localization.')
  776. save_merged_map = False
  777. original_robot_map_frame_pose, corrected_robot_map_frame_pose, original_robot_map_image_pose, corrected_robot_map_image_pose, scaled_scan, scaled_merged_map = ma.localize_with_reduced_images(head_scan, self.merged_map, global_localization=True, divisor=6) #4)
  778. corrected_robot_map_pose = corrected_robot_map_frame_pose
  779. original_robot_map_pose = original_robot_map_frame_pose
  780. self.localized = True
  781. # Save the scaled scans to disk for debugging.
  782. if self.debug_directory is not None:
  783. dirname = self.debug_directory + 'scaled_localization_scans/'
  784. # If the directory does not already exist, create it.
  785. if not os.path.exists(dirname):
  786. os.makedirs(dirname)
  787. time_string = hm.create_time_string()
  788. filename = 'localization_scaled_head_scan_' + time_string
  789. scaled_scan.save(dirname + filename)
  790. filename = 'localization_scaled_merged_map_' + time_string
  791. scaled_merged_map.save(dirname + filename)
  792. else:
  793. rospy.loginfo('FunmapNode perform_head_scan: No debug directory provided, so debugging data will not be saved.')
  794. else:
  795. # The robot has been localized with respect to the
  796. # current map, so proceed to merge the new head scan
  797. # into the map. This assumes that the robot's
  798. # estimated pose is close to its actual pose in the
  799. # map. It constrains the matching optimization to a
  800. # limited range of positions and orientations.
  801. rospy.loginfo('perform_head_scan: Performing local map merge.')
  802. original_robot_map_pose, corrected_robot_map_pose = mm.merge_scan_1_into_scan_2(head_scan, self.merged_map)
  803. save_merged_map = True
  804. # Store the corrected robot pose relative to the map frame.
  805. self.robot_poses.append(corrected_robot_map_pose)
  806. self.correct_robot_pose(original_robot_map_pose, corrected_robot_map_pose)
  807. pub_robot_markers = True
  808. if pub_robot_markers:
  809. self.publish_corrected_robot_pose_markers(original_robot_map_pose, corrected_robot_map_pose)
  810. if save_merged_map:
  811. # If the merged map has been updated, save it to disk.
  812. if self.debug_directory is not None:
  813. head_scans_dirname = self.debug_directory + 'head_scans/'
  814. # If the directory does not already exist, create it.
  815. if not os.path.exists(head_scans_dirname):
  816. os.makedirs(head_scans_dirname)
  817. merged_maps_dirname = self.debug_directory + 'merged_maps/'
  818. # If the directory does not already exist, create it.
  819. if not os.path.exists(merged_maps_dirname):
  820. os.makedirs(merged_maps_dirname)
  821. time_string = hm.create_time_string()
  822. if scaled_scan is not None:
  823. filename = 'localization_scaled_head_scan_' + time_string
  824. scaled_scan.save(head_scans_dirname + filename)
  825. if scaled_merged_map is not None:
  826. filename = 'localization_scaled_merged_map_' + time_string
  827. scaled_merged_map.save(merged_maps_dirname + filename)
  828. filename = 'merged_map_' + hm.create_time_string()
  829. self.merged_map.save(merged_maps_dirname + filename)
  830. else:
  831. rospy.loginfo('FunmapNode perform_head_scan: No debug directory provided, so debugging data will not be saved.')
  832. if fill_in_blindspot_with_second_scan and (not localize_only):
  833. # Turn the robot to the left in attempt to fill in its
  834. # blindspot due to its mast.
  835. turn_ang = (70.0/180.0) * np.pi
  836. # Command the robot to turn to point to the next
  837. # waypoint.
  838. rospy.loginfo('robot turn angle in degrees =' + str(turn_ang * (180.0/np.pi)))
  839. at_goal = self.move_base.turn(turn_ang, publish_visualizations=True)
  840. if not at_goal:
  841. message_text = 'Failed to reach turn goal.'
  842. rospy.loginfo(message_text)
  843. self.perform_head_scan(fill_in_blindspot_with_second_scan=False)
  844. def get_plan_service_callback(self, request):
  845. # request.start, request.goal, request.tolerance
  846. goal_pose = request.goal
  847. end_xy = self.pose_to_map_pixel(goal_pose)
  848. if end_xy is None:
  849. message = 'Failed to convert pose to map pixel.'
  850. rospy.logerr(message)
  851. return
  852. path, message = self.plan_a_path(end_xy)
  853. plan = Path()
  854. header = plan.header
  855. time_stamp = rospy.Time.now()
  856. header.stamp = time_stamp
  857. header.frame_id = 'map'
  858. if path is None:
  859. rospy.logerr(message)
  860. return plan
  861. # Existence of the merged map is checked by plan_a_path, but
  862. # to avoid future issues I'm introducing this redundancy.
  863. if self.merged_map is None:
  864. success = False
  865. return success, 'No map available for planning and navigation.'
  866. max_height_im = self.merged_map.max_height_im
  867. map_frame_id = self.merged_map.max_height_im.voi.frame_id
  868. # Query TF2 to obtain the current estimated transformation
  869. # from the map image to the map frame.
  870. image_to_points_mat, ip_timestamp = max_height_im.get_image_to_points_mat(map_frame_id, self.tf2_buffer)
  871. if image_to_points_mat is None:
  872. rospy.logerr('image_to_points_mat unavailable via TF2')
  873. return plan
  874. path_height_m = 0.0
  875. for xyz in path:
  876. image_point = np.array([xyz[0], xyz[1], 0.0, 1.0])
  877. map_point = np.matmul(image_to_points_mat, image_point)
  878. p = PoseStamped()
  879. p.header.frame_id = 'map'
  880. p.header.stamp = time_stamp
  881. p.pose.position.x = map_point[0]
  882. p.pose.position.y = map_point[1]
  883. p.pose.position.z = path_height_m
  884. plan.poses.append(p)
  885. return plan
  886. def correct_robot_pose(self, original_robot_map_pose_xya, corrected_robot_map_pose_xya):
  887. # Compute and broadcast the corrected transformation from
  888. # the map frame to the odom frame.
  889. print('original_robot_map_pose_xya =', original_robot_map_pose_xya)
  890. print('corrected_robot_map_pose_xya =', corrected_robot_map_pose_xya)
  891. x_delta = corrected_robot_map_pose_xya[0] - original_robot_map_pose_xya[0]
  892. y_delta = corrected_robot_map_pose_xya[1] - original_robot_map_pose_xya[1]
  893. ang_rad_correction = hm.angle_diff_rad(corrected_robot_map_pose_xya[2], original_robot_map_pose_xya[2])
  894. c = np.cos(ang_rad_correction)
  895. s = np.sin(ang_rad_correction)
  896. rot_mat = np.array([[c, -s], [s, c]])
  897. x_old, y_old, a_old = original_robot_map_pose_xya
  898. xy_old = np.array([x_old, y_old])
  899. tx, ty = np.matmul(rot_mat, -xy_old) + np.array([x_delta, y_delta]) + xy_old
  900. t = np.identity(4)
  901. t[0,3] = tx
  902. t[1,3] = ty
  903. t[:2,:2] = rot_mat
  904. self.map_to_odom_transform_mat = np.matmul(t, self.map_to_odom_transform_mat)
  905. self.tf2_broadcaster.sendTransform(create_map_to_odom_transform(self.map_to_odom_transform_mat))
  906. def publish_corrected_robot_pose_markers(self, original_robot_map_pose_xya, corrected_robot_map_pose_xya):
  907. # Publish markers to visualize the corrected and
  908. # uncorrected robot poses on the map.
  909. timestamp = rospy.Time.now()
  910. markers = MarkerArray()
  911. ang_rad = corrected_robot_map_pose_xya[2]
  912. x_axis = [np.cos(ang_rad), np.sin(ang_rad), 0.0]
  913. x, y, a = corrected_robot_map_pose_xya
  914. point = [x, y, 0.1]
  915. rgba = [0.0, 1.0, 0.0, 0.5]
  916. m_id = 0
  917. m = hr.create_sphere_marker(point, m_id, 'map', timestamp, rgba=rgba, diameter_m=0.1, duration_s=0.0)
  918. markers.markers.append(m)
  919. m_id += 1
  920. m = hr.create_axis_marker(point, x_axis, m_id, 'map', timestamp, rgba, length=0.2, arrow_scale=3.0)
  921. markers.markers.append(m)
  922. m_id += 1
  923. x, y, a = original_robot_map_pose_xya
  924. point = [x, y, 0.1]
  925. rgba = [1.0, 0.0, 0.0, 0.5]
  926. m = hr.create_sphere_marker(point, m_id, 'map', timestamp, rgba=rgba, diameter_m=0.1, duration_s=0.0)
  927. markers.markers.append(m)
  928. m_id += 1
  929. m = hr.create_axis_marker(point, x_axis, m_id, 'map', timestamp, rgba, length=0.2, arrow_scale=3.0)
  930. markers.markers.append(m)
  931. m_id += 1
  932. self.marker_array_pub.publish(markers)
  933. def set_robot_pose_callback(self, pose_with_cov_stamped):
  934. rospy.loginfo('Set robot pose called. This will set the pose of the robot on the map.')
  935. rospy.loginfo(pose_with_cov_stamped)
  936. original_robot_map_pose_xya, timestamp = self.get_robot_floor_pose_xya(floor_frame='map')
  937. pwcs = pose_with_cov_stamped
  938. frame_id = pwcs.header.frame_id
  939. timestamp = pwcs.header.stamp
  940. pose = pwcs.pose.pose
  941. if frame_id != 'map':
  942. lookup_time = rospy.Time(0) # return most recent transform
  943. timeout_ros = rospy.Duration(0.1)
  944. stamped_transform = tf2_buffer.lookup_transform('map', frame_id, lookup_time, timeout_ros)
  945. map_pose = do_transform_pose(pose, stamped_transform)
  946. else:
  947. map_pose = pose
  948. p = map_pose.position
  949. q = map_pose.orientation
  950. q_list = [q.x, q.y, q.z, q.w]
  951. x = p.x
  952. y = p.y
  953. z = p.z
  954. roll, pitch, yaw = euler_from_quaternion(q_list)
  955. corrected_robot_map_pose_xya = [x, y, yaw]
  956. self.correct_robot_pose(original_robot_map_pose_xya, corrected_robot_map_pose_xya)
  957. self.publish_corrected_robot_pose_markers(original_robot_map_pose_xya, corrected_robot_map_pose_xya)
  958. def navigate_to_goal_topic_callback(self, goal_pose):
  959. rospy.loginfo('Navigate to goal simple navigate to goal topic received a command!')
  960. rospy.loginfo(goal_pose)
  961. end_xy = self.pose_to_map_pixel(goal_pose)
  962. if end_xy is None:
  963. message = 'Failed to convert pose to map pixel.'
  964. rospy.logerr(message)
  965. return
  966. success, message = self.navigate_to_map_pixel(end_xy)
  967. if success:
  968. rospy.loginfo(message)
  969. else:
  970. rospy.logerr(message)
  971. return
  972. def navigate_to_goal_action_callback(self, goal):
  973. # geometry_msgs/PoseStamped target_pose
  974. goal_pose = goal.target_pose
  975. rospy.loginfo('Navigate to goal simple action server received a command!')
  976. rospy.loginfo(goal_pose)
  977. end_xy = self.pose_to_map_pixel(goal_pose)
  978. if end_xy is None:
  979. message = 'Failed to convert pose to map pixel.'
  980. rospy.logerr(message)
  981. self.navigate_to_goal_action_server.set_aborted()
  982. return
  983. success, message = self.navigate_to_map_pixel(end_xy)
  984. if success:
  985. result = MoveBaseResult()
  986. self.navigate_to_goal_action_server.set_succeeded(result)
  987. else:
  988. rospy.logerr(message)
  989. self.navigate_to_goal_action_server.set_aborted()
  990. return
  991. def main(self):
  992. hm.HelloNode.main(self, 'funmap', 'funmap')
  993. self.debug_directory = rospy.get_param('~debug_directory')
  994. self.merged_map = None
  995. self.localized = False
  996. if self.map_filename is not None:
  997. self.merged_map = ma.HeadScan.from_file(self.map_filename)
  998. self.localized = False
  999. ###########################
  1000. # Related to move_base API
  1001. self.navigate_to_goal_action_server = actionlib.SimpleActionServer('/move_base',
  1002. MoveBaseAction,
  1003. execute_cb = self.navigate_to_goal_action_callback,
  1004. auto_start = False)
  1005. self.navigate_to_goal_action_server.start()
  1006. self.navigation_goal_subscriber = rospy.Subscriber('/move_base_simple/goal',
  1007. PoseStamped,
  1008. self.navigate_to_goal_topic_callback)
  1009. self.set_robot_pose_subscriber = rospy.Subscriber('/initialpose', PoseWithCovarianceStamped, self.set_robot_pose_callback)
  1010. self.get_plan_service = rospy.Service('/make_plan',
  1011. GetPlan,
  1012. self.get_plan_service_callback)
  1013. ###########################
  1014. self.trigger_head_scan_service = rospy.Service('/funmap/trigger_head_scan',
  1015. Trigger,
  1016. self.trigger_head_scan_service_callback)
  1017. self.trigger_drive_to_scan_service = rospy.Service('/funmap/trigger_drive_to_scan',
  1018. Trigger,
  1019. self.trigger_drive_to_scan_service_callback)
  1020. self.trigger_global_localization_service = rospy.Service('/funmap/trigger_global_localization',
  1021. Trigger,
  1022. self.trigger_global_localization_service_callback)
  1023. self.trigger_local_localization_service = rospy.Service('/funmap/trigger_local_localization',
  1024. Trigger,
  1025. self.trigger_local_localization_service_callback)
  1026. self.trigger_align_with_nearest_cliff_service = rospy.Service('/funmap/trigger_align_with_nearest_cliff',
  1027. Trigger,
  1028. self.trigger_align_with_nearest_cliff_service_callback)
  1029. self.trigger_reach_until_contact_service = rospy.Service('/funmap/trigger_reach_until_contact',
  1030. Trigger,
  1031. self.trigger_reach_until_contact_service_callback)
  1032. self.trigger_lower_until_contact_service = rospy.Service('/funmap/trigger_lower_until_contact',
  1033. Trigger,
  1034. self.trigger_lower_until_contact_service_callback)
  1035. self.reach_to_click_subscriber = rospy.Subscriber('/clicked_point', PointStamped, self.reach_to_click_callback)
  1036. default_service = '/camera/switch_to_default_mode'
  1037. high_accuracy_service = '/camera/switch_to_high_accuracy_mode'
  1038. rospy.loginfo('Node ' + self.node_name + ' waiting to connect to ' + default_service + ' and ' + high_accuracy_service)
  1039. rospy.wait_for_service(default_service)
  1040. rospy.loginfo('Node ' + self.node_name + ' connected to ' + default_service)
  1041. self.trigger_d435i_default_mode_service = rospy.ServiceProxy(default_service, Trigger)
  1042. rospy.wait_for_service(high_accuracy_service)
  1043. rospy.loginfo('Node ' + self.node_name + ' connected to' + high_accuracy_service)
  1044. self.trigger_d435i_high_accuracy_mode_service = rospy.ServiceProxy(high_accuracy_service, Trigger)
  1045. self.tf2_broadcaster = tf2_ros.TransformBroadcaster()
  1046. self.point_cloud_pub = rospy.Publisher('/funmap/point_cloud2', PointCloud2, queue_size=1)
  1047. self.voi_marker_pub = rospy.Publisher('/funmap/voi_marker', Marker, queue_size=1)
  1048. self.marker_array_pub = rospy.Publisher('/funmap/marker_array', MarkerArray, queue_size=1)
  1049. self.navigation_plan_markers_pub = rospy.Publisher('/funmap/navigation_plan_markers', MarkerArray, queue_size=1)
  1050. self.obstacle_point_cloud_pub = rospy.Publisher('/funmap/obstacle_point_cloud2', PointCloud2, queue_size=1)
  1051. self.joint_states_subscriber = rospy.Subscriber('/stretch/joint_states', JointState, self.joint_states_callback)
  1052. self.rate = 5.0
  1053. rate = rospy.Rate(self.rate)
  1054. self.move_base = nv.MoveBase(self, self.debug_directory)
  1055. self.map_to_odom_transform_mat = np.identity(4)
  1056. while not rospy.is_shutdown():
  1057. self.tf2_broadcaster.sendTransform(create_map_to_odom_transform(self.map_to_odom_transform_mat))
  1058. self.publish_map_point_cloud()
  1059. rate.sleep()
  1060. if __name__ == '__main__':
  1061. try:
  1062. parser = ap.ArgumentParser(description='Keyboard teleoperation for stretch.')
  1063. parser.add_argument('--load_map', default=None, help='Provide directory from which to load a map.')
  1064. args, unknown = parser.parse_known_args()
  1065. map_filename = args.load_map if args.load_map else None
  1066. node = FunmapNode(map_filename)
  1067. node.main()
  1068. rospy.spin()
  1069. except KeyboardInterrupt:
  1070. print('interrupt received, so shutting down')