line_planner_node.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 from xmlrpc.client import Boolean
3 import rospy
4 import math
5 import tf
6 import tf2_ros
7 
8 from utils import *
9 
10 from geometry_msgs.msg import Twist, Point
11 from visualization_msgs.msg import Marker, MarkerArray
12 from geometry_msgs.msg import PoseStamped
13 from std_msgs.msg import Empty, ColorRGBA, Bool
14 
15 from tf.transformations import euler_from_quaternion
16 from tf2_geometry_msgs import do_transform_pose
17 
18 from dynamic_reconfigure.server import Server as DynamicReconfigureServer
19 
20 from line_planner.cfg import LinePlannerConfig
21 from nav_msgs.msg import Path
22 
23 ROBOT_FRAME = "base_link"
24 PLANNING_FRAME = "map"
25 
26 class GoalServer:
27 
28  def __init__(self, tf2_buffer, update_plan):
29  self.start_goal = None
30  self.end_goal = None
31  self.tf2_buffer = tf2_buffer
32  self.update_plan = update_plan
33 
34  self.simple_goal_sub = rospy.Subscriber("/move_base_simple/waypoints", Path, self.route_callback)
35  self.simple_goal_sub = rospy.Subscriber("/move_base_simple/goal", PoseStamped, self.goal_callback)
36  self.clear_goals_sub = rospy.Subscriber("/move_base_simple/clear", Empty, self.reset)
37 
38  self.start_goal = None
39  self.end_goal = None
40  self.route = []
41  self.route_index = 0
42 
43  def reset(self,msg):
44  self.start_goal = None
45  self.end_goal = None
46  self.route = []
47  self.route_index = 0
48  self.update_plan()
49 
50  def goal_callback(self, goal):
51  self.route = []
52  self.route_index = 0
53  self.process_goal(goal)
54  self.update_plan()
55 
56  def route_callback(self, msg):
57  rospy.loginfo("New route received.")
58 
59  if len(msg.poses) == 0:
60  rospy.loginfo("Empty route, stopping.")
61  self.reset(None)
62  return
63 
64  self.route = msg.poses
65  self.route_index = 0
66  self.process_goal(self.route[0])
67  self.update_plan()
68 
69  def get_goals(self):
70  return self.start_goal, self.end_goal
71 
72  def goal_reached(self):
73  if len(self.route) > 0:
74  rospy.loginfo("Goal #%i reached.",self.route_index)
75  if self.route_index < len(self.route)-1:
76  self.route_index +=1
77  self.process_goal(self.route[self.route_index])
78  else:
79  rospy.loginfo("-> Route finished.")
80  self.start_goal = None
81  self.end_goal = None
82  else:
83  rospy.loginfo("Simple goal reached.")
84  self.start_goal = None
85  self.end_goal = None
86 
87  self.update_plan()
88 
89 
90  def set_goal_pair(self, endgoal):
91  if len(self.route) == 0 or self.route_index == 0:
92  try:
93  self.start_goal = transform_to_pose(self.tf2_buffer.lookup_transform(PLANNING_FRAME, ROBOT_FRAME, rospy.Time(0)))
94  self.end_goal = endgoal
95  except (tf2_ros.LookupException, tf2_ros.ConnectivityException, tf2_ros.ExtrapolationException) as e:
96  rospy.logwarn("TF2 exception: %s", e)
97  else:
98  self.start_goal = self.end_goal
99  self.end_goal = endgoal
100 
101  self.update_plan()
102 
103  def process_goal(self, goal):
104  if PLANNING_FRAME == goal.header.frame_id:
105  rospy.loginfo("------------------")
106  rospy.loginfo("Received goal in planning ("+PLANNING_FRAME+") frame.")
107  rospy.loginfo("Position: X: %f, Y: %f, Z: %f", goal.pose.position.x, goal.pose.position.y, goal.pose.position.z)
108  rospy.loginfo("Orientation: X: %f, Y: %f, Z: %f, W: %f", goal.pose.orientation.x, goal.pose.orientation.y, goal.pose.orientation.z, goal.pose.orientation.w)
109  self.set_goal_pair(goal.pose)
110  else:
111  try:
112  transform = self.tf2_buffer.lookup_transform(PLANNING_FRAME, goal.header.frame_id, rospy.Time(0))
113  goal_transformed = do_transform_pose(goal, transform)
114  self.set_goal_pair(goal_transformed.pose)
115  rospy.loginfo("------------------")
116  rospy.loginfo("Received goal in "+goal.header.frame_id+" frame, transformed to "+PLANNING_FRAME+".")
117  rospy.loginfo("Position: X: %f, Y: %f, Z: %f", goal_transformed.pose.position.x, goal_transformed.pose.position.y, goal_transformed.pose.position.z)
118  rospy.loginfo("Orientation: X: %f, Y: %f, Z: %f, W: %f", goal_transformed.pose.orientation.x, goal_transformed.pose.orientation.y, goal_transformed.pose.orientation.z, goal_transformed.pose.orientation.w)
119 
120  except (tf2_ros.LookupException, tf2_ros.ConnectivityException, tf2_ros.ExtrapolationException) as e:
121  rospy.logwarn("TF2 exception: %s", e)
122  return
123 
125  def __init__(self):
126  rospy.init_node("line_following_controller")
127  global ROBOT_FRAME, PLANNING_FRAME
128 
129  ROBOT_FRAME = rospy.get_param('~robot_frame', 'base_link')
130  PLANNING_FRAME = rospy.get_param('~planning_frame', 'map')
131 
132  self.MIN_GOAL_DIST = rospy.get_param('~goal_distance_threshold', 0.6)
133 
134  self.MAX_ANGULAR_SPD = rospy.get_param('~max_turning_velocity', 0.9)
135 
136  self.LINEAR_ACCEL = rospy.get_param('~linear_acceleration', 0.1)
137  self.MIN_LINEAR_SPD = rospy.get_param('~min_linear_velocity', 0.1)
138  self.MAX_LINEAR_SPD = rospy.get_param('max_linear_velocity', 0.45)
139 
140  self.LINE_DIVERGENCE = rospy.get_param('~max_line_divergence', 1.0)
141  self.MIN_PROJECT_DIST = rospy.get_param('~min_project_dist', 0.15)
142  self.MAX_PROJECT_DIST = rospy.get_param('~max_project_dist', 1.2)
143 
144  self.SIDE_OFFSET_MULT = rospy.get_param('~side_offset_mult', 0.5)
145 
146  self.DEBUG_MARKERS = rospy.get_param('~publish_debug_markers', True)
147 
148  self.tf_listener = tf.TransformListener()
149 
150  self.tf2_buffer = tf2_ros.Buffer()
151  self.tf2_listener = tf2_ros.TransformListener(self.tf2_buffer)
152 
153  self.cmd_vel_pub = rospy.Publisher("cmd_vel", Twist, queue_size=1)
154 
155  self.status_pub = rospy.Publisher("line_planner/active", Bool, queue_size=1, latch=True)
156  self.plan_pub = rospy.Publisher("line_planner/plan", Path, queue_size=1, latch=True)
157  self.marker_pub = rospy.Publisher("line_planner/markers", MarkerArray, queue_size=1)
158 
159  self.pid = PID(
160  rospy.get_param('P', 3.0),
161  rospy.get_param('I', 0.001),
162  rospy.get_param('D', 65.0)
163  )
164 
166  self.active = False
167 
168  self.reconfigure_server = DynamicReconfigureServer(LinePlannerConfig, self.dynamic_reconfigure_callback)
169 
171  self.status_pub.publish(False)
172 
173  rospy.loginfo("Line planner started.")
174  rospy.loginfo("Robot frame: "+ROBOT_FRAME)
175  rospy.loginfo("Planning frame: "+PLANNING_FRAME)
176 
177  def dynamic_reconfigure_callback(self, config, level):
178  self.pid.kp = config.P
179  self.pid.ki = config.I
180  self.pid.kd = config.D
181 
182  self.MIN_GOAL_DIST = config.goal_distance_threshold
183 
184  self.LINEAR_ACCEL = config.linear_acceleration
185  self.MAX_LINEAR_SPD = config.max_linear_velocity
186 
187  self.MAX_ANGULAR_SPD = config.max_turning_velocity
188  self.MAX_LINEAR_SPD = config.max_linear_velocity
189 
190  self.LINE_DIVERGENCE = config.max_line_divergence
191  self.MIN_PROJECT_DIST = config.min_project_dist
192  self.MAX_PROJECT_DIST = config.max_project_dist
193 
194  self.DEBUG_MARKERS = config.publish_debug_markers
195 
196  return config
197 
198  def get_angle_error(self, current_pose, target_position):
199  _, _, current_yaw = euler_from_quaternion([
200  current_pose.orientation.x,
201  current_pose.orientation.y,
202  current_pose.orientation.z,
203  current_pose.orientation.w,
204  ])
205 
206  target_unit_vector = get_dir(current_pose.position, target_position)
207  current_unit_vector = [math.cos(current_yaw), math.sin(current_yaw)]
208 
209  #get the angle cosine
210  dot_product = target_unit_vector[0] * current_unit_vector[0] + target_unit_vector[1] * current_unit_vector[1]
211 
212  #get the direction
213  cross_product = current_unit_vector[0] * target_unit_vector[1] - current_unit_vector[1] * target_unit_vector[0]
214 
215  return -math.copysign(math.acos(dot_product), cross_product)
216 
217  def get_distance(self, pose, goal):
218  deltax = goal.position.x - pose.position.x
219  deltay = goal.position.y - pose.position.y
220  return math.sqrt(deltax** 2 + deltay ** 2)
221 
222  def get_linear_velocity(self, distance, angle_error):
223  vel = self.MAX_LINEAR_SPD
224 
225  # check for correct orientation
226  abserr = math.fabs(angle_error)
227 
228  if abserr > 2.0:
229  vel *= clamp(-1.598 * abserr + 3.196, -1.0, 0.0) # gradually reverse from 120 to 180 deg heading
230  return clamp(vel, -self.MAX_LINEAR_SPD, 0.0)
231 
232  if abserr > 0.52:
233  vel *= clamp((-1.0 / 0.52) * abserr + 2, 0.0, 1.0) # gradually decrease velocity from 30 to 60 deg heading
234 
235  return clamp(vel, 0.0, self.MAX_LINEAR_SPD)
236 
237 
238  def update(self):
239  start_goal, end_goal = self.goal_server.get_goals()
240 
241  if start_goal == None or end_goal == None:
242 
243  if self.active:
244  self.send_twist(0, 0)
245  if self.DEBUG_MARKERS:
246  self.delete_debug_markers()
247  self.active = False
248  self.status_pub.publish(False)
249  return
250 
251  try:
252  self.active = True
253  self.status_pub.publish(True)
254  pose = transform_to_pose(self.tf2_buffer.lookup_transform(PLANNING_FRAME, ROBOT_FRAME, rospy.Time(0)))
255 
256  target_position = project_position(
257  start_goal,
258  end_goal,
259  pose,
260  self.MIN_PROJECT_DIST,
261  self.MAX_PROJECT_DIST,
262  self.LINE_DIVERGENCE,
263  self.SIDE_OFFSET_MULT
264  )
265 
266  angle_error = self.get_angle_error(pose, target_position)
267  angular_velocity = clamp(self.pid.compute(angle_error), -self.MAX_ANGULAR_SPD, self.MAX_ANGULAR_SPD)
268  target_distance = self.get_distance(end_goal, pose)
269 
270  if target_distance > self.MIN_GOAL_DIST:
271  linear_velocity = self.get_linear_velocity(target_distance, angle_error)
272  else:
273  linear_velocity = 0
274  angular_velocity = 0
275  self.goal_server.goal_reached()
276 
277  self.send_twist(linear_velocity, angular_velocity)
278 
279  if self.DEBUG_MARKERS:
280  self.draw_debug_markers(target_position, start_goal, end_goal)
281 
282  except (tf.LookupException, tf.ConnectivityException, tf.ExtrapolationException):
283  rospy.logwarn("TF Exception")
284 
285  def update_plan(self):
286 
287  msg = Path()
288  msg.header.frame_id = PLANNING_FRAME
289 
290  if len(self.goal_server.route) > 1:
291  msg.poses = self.goal_server.route[self.goal_server.route_index:]
292  else:
293  start_goal, end_goal = self.goal_server.get_goals()
294 
295  if start_goal == None or end_goal == None:
296  self.plan_pub.publish(Path())
297  return
298 
299  start_stamped = PoseStamped()
300  start_stamped.header.frame_id = PLANNING_FRAME
301  start_stamped.pose = start_goal
302  msg.poses.append(start_stamped)
303 
304  end_stamped = PoseStamped()
305  end_stamped.header.frame_id = PLANNING_FRAME
306  end_stamped.pose = end_goal
307 
308  msg.poses = [start_stamped, end_stamped]
309 
310  self.plan_pub.publish(msg)
311 
312  def send_twist(self, vel_x, vel_z):
313 
314  #sanity check, just in case
315  if math.isnan(vel_x):
316  vel_x = 0
317 
318  if math.isnan(vel_z):
319  vel_z = 0
320 
321  twist = Twist()
322  twist.linear.x = vel_x
323  twist.angular.z = vel_z
324  self.cmd_vel_pub.publish(twist)
325 
326  def cleanup(self):
327  self.send_twist(0,0)
328  self.delete_debug_markers()
329 
331 
332  marker = Marker()
333  marker.action = 3
334 
335  markerArray = MarkerArray()
336  markerArray.markers.append(marker)
337  self.marker_pub.publish(markerArray)
338 
339  def draw_debug_markers(self, target_position, start_goal, end_goal):
340 
341  def sphere_marker(position, marker_id, r, g, b, size):
342  marker = Marker()
343  marker.header.frame_id = PLANNING_FRAME
344  marker.type = Marker.SPHERE
345  marker.pose.position = position
346  marker.pose.orientation.w = 1.0
347  marker.scale.x = size
348  marker.scale.y = size
349  marker.scale.z = size
350  marker.color.a = 0.5
351  marker.color.r = r
352  marker.color.g = g
353  marker.color.b = b
354  marker.id = marker_id
355  return marker
356 
357  def line_marker(p_from, p_to, marker_id, r, g, b):
358  marker = Marker()
359  marker.header.frame_id = PLANNING_FRAME
360  marker.type = Marker.LINE_STRIP
361  marker.pose.orientation.w = 1.0
362 
363  marker.points = [
364  Point(p_from.x, p_from.y, p_from.z),
365  Point(p_to.x, p_to.y, p_to.z),
366  ]
367 
368  c = ColorRGBA(r,g,b, 1.0)
369 
370  marker.colors = [c, c]
371 
372  marker.scale.x = 0.05
373  marker.id = marker_id
374  return marker
375 
376  # to avoid flooding
377  if self.marker_publish_skip == 0:
378  markerArray = MarkerArray()
379  markerArray.markers.append(line_marker(start_goal.position, end_goal.position, 0, 0.575, 0.870, 0.0261))
380  markerArray.markers.append(sphere_marker(start_goal.position, 1, 1.0, 0.0, 0.0, 0.2))
381  markerArray.markers.append(sphere_marker(end_goal.position, 2, 0.0, 0.0, 1.0, self.MIN_GOAL_DIST*2))
382  markerArray.markers.append(sphere_marker(target_position, 3, 0.0, 1.0, 0.0, 0.2))
383  self.marker_pub.publish(markerArray)
384 
385  self.marker_publish_skip = (self.marker_publish_skip +1)%5
386 
387 
388 
390 rate = rospy.Rate(rospy.get_param('rate', 30))
391 rospy.on_shutdown(ctrl.cleanup)
392 
393 while not rospy.is_shutdown():
394  ctrl.update()
395  rate.sleep()
line_planner_node.LineFollowingController.update_plan
def update_plan(self)
Definition: line_planner_node.py:285
line_planner_node.LineFollowingController.pid
pid
Definition: line_planner_node.py:159
line_planner_node.LineFollowingController.LINEAR_ACCEL
LINEAR_ACCEL
Definition: line_planner_node.py:136
line_planner_node.LineFollowingController.plan_pub
plan_pub
Definition: line_planner_node.py:156
line_planner_node.GoalServer.get_goals
def get_goals(self)
Definition: line_planner_node.py:69
line_planner_node.GoalServer.process_goal
def process_goal(self, goal)
Definition: line_planner_node.py:103
line_planner_node.LineFollowingController.get_angle_error
def get_angle_error(self, current_pose, target_position)
Definition: line_planner_node.py:198
line_planner_node.GoalServer.update_plan
update_plan
Definition: line_planner_node.py:32
line_planner_node.LineFollowingController.get_linear_velocity
def get_linear_velocity(self, distance, angle_error)
Definition: line_planner_node.py:222
line_planner_node.LineFollowingController.delete_debug_markers
def delete_debug_markers(self)
Definition: line_planner_node.py:330
line_planner_node.GoalServer.route
route
Definition: line_planner_node.py:40
line_planner_node.LineFollowingController.LINE_DIVERGENCE
LINE_DIVERGENCE
Definition: line_planner_node.py:140
line_planner_node.LineFollowingController.status_pub
status_pub
Definition: line_planner_node.py:155
line_planner_node.LineFollowingController.goal_server
goal_server
Definition: line_planner_node.py:165
line_planner_node.GoalServer.goal_reached
def goal_reached(self)
Definition: line_planner_node.py:72
line_planner_node.LineFollowingController.MIN_PROJECT_DIST
MIN_PROJECT_DIST
Definition: line_planner_node.py:141
line_planner_node.LineFollowingController.SIDE_OFFSET_MULT
SIDE_OFFSET_MULT
Definition: line_planner_node.py:144
line_planner_node.LineFollowingController.cmd_vel_pub
cmd_vel_pub
Definition: line_planner_node.py:153
line_planner_node.LineFollowingController.marker_publish_skip
marker_publish_skip
Definition: line_planner_node.py:170
line_planner_node.LineFollowingController.tf_listener
tf_listener
Definition: line_planner_node.py:148
line_planner_node.LineFollowingController.reconfigure_server
reconfigure_server
Definition: line_planner_node.py:168
line_planner_node.GoalServer.route_index
route_index
Definition: line_planner_node.py:41
line_planner_node.LineFollowingController
Definition: line_planner_node.py:124
line_planner_node.GoalServer.simple_goal_sub
simple_goal_sub
Definition: line_planner_node.py:34
utils.clamp
def clamp(num, min, max)
Definition: utils.py:51
line_planner_node.LineFollowingController.MAX_PROJECT_DIST
MAX_PROJECT_DIST
Definition: line_planner_node.py:142
line_planner_node.GoalServer
Definition: line_planner_node.py:26
utils.PID
Definition: utils.py:75
line_planner_node.LineFollowingController.marker_pub
marker_pub
Definition: line_planner_node.py:157
line_planner_node.GoalServer.reset
def reset(self, msg)
Definition: line_planner_node.py:43
line_planner_node.LineFollowingController.tf2_buffer
tf2_buffer
Definition: line_planner_node.py:150
line_planner_node.GoalServer.start_goal
start_goal
Definition: line_planner_node.py:29
line_planner_node.GoalServer.tf2_buffer
tf2_buffer
Definition: line_planner_node.py:31
utils.get_dir
def get_dir(from_vec, to_vec)
Definition: utils.py:66
line_planner_node.LineFollowingController.MIN_GOAL_DIST
MIN_GOAL_DIST
Definition: line_planner_node.py:132
line_planner_node.GoalServer.__init__
def __init__(self, tf2_buffer, update_plan)
Definition: line_planner_node.py:28
line_planner_node.LineFollowingController.send_twist
def send_twist(self, vel_x, vel_z)
Definition: line_planner_node.py:312
line_planner_node.LineFollowingController.MIN_LINEAR_SPD
MIN_LINEAR_SPD
Definition: line_planner_node.py:137
line_planner_node.LineFollowingController.active
active
Definition: line_planner_node.py:166
utils.transform_to_pose
def transform_to_pose(t)
Definition: utils.py:54
line_planner_node.LineFollowingController.__init__
def __init__(self)
Definition: line_planner_node.py:125
line_planner_node.LineFollowingController.DEBUG_MARKERS
DEBUG_MARKERS
Definition: line_planner_node.py:146
line_planner_node.LineFollowingController.tf2_listener
tf2_listener
Definition: line_planner_node.py:151
line_planner_node.GoalServer.route_callback
def route_callback(self, msg)
Definition: line_planner_node.py:56
line_planner_node.LineFollowingController.draw_debug_markers
def draw_debug_markers(self, target_position, start_goal, end_goal)
Definition: line_planner_node.py:339
line_planner_node.LineFollowingController.update
def update(self)
Definition: line_planner_node.py:238
line_planner_node.LineFollowingController.cleanup
def cleanup(self)
Definition: line_planner_node.py:326
line_planner_node.GoalServer.end_goal
end_goal
Definition: line_planner_node.py:30
line_planner_node.GoalServer.clear_goals_sub
clear_goals_sub
Definition: line_planner_node.py:36
utils.project_position
def project_position(start, end, current, mindist, maxdist, line_divergence, side_offset)
Definition: utils.py:8
line_planner_node.GoalServer.goal_callback
def goal_callback(self, goal)
Definition: line_planner_node.py:50
line_planner_node.GoalServer.set_goal_pair
def set_goal_pair(self, endgoal)
Definition: line_planner_node.py:90
line_planner_node.LineFollowingController.get_distance
def get_distance(self, pose, goal)
Definition: line_planner_node.py:217
line_planner_node.LineFollowingController.MAX_LINEAR_SPD
MAX_LINEAR_SPD
Definition: line_planner_node.py:138
line_planner_node.LineFollowingController.MAX_ANGULAR_SPD
MAX_ANGULAR_SPD
Definition: line_planner_node.py:134
line_planner_node.LineFollowingController.dynamic_reconfigure_callback
def dynamic_reconfigure_callback(self, config, level)
Definition: line_planner_node.py:177


line_planner
Author(s):
autogenerated on Sat Jun 10 2023 02:19:03