7
7
# Standard imports
8
8
import math
9
9
import time
10
- from typing import List , Optional
11
10
12
11
# Third-party imports
13
12
from action_msgs .msg import GoalStatus
14
13
from moveit_msgs .msg import MoveItErrorCodes
15
14
import py_trees
16
15
from pymoveit2 import MoveIt2State
17
- from pymoveit2 .robots import kinova
18
16
from rclpy .node import Node
19
- from rclpy .qos import QoSPresetProfiles
20
- from sensor_msgs .msg import JointState
21
17
from trajectory_msgs .msg import JointTrajectory
22
18
23
19
# Local imports
@@ -145,16 +141,6 @@ def __init__(
145
141
self .node ,
146
142
)
147
143
148
- # Subscribe to the joint state and track the distance to goal while the
149
- # robot is executing the trajectory.
150
- self .distance_to_goal = DistanceToGoal ()
151
- node .create_subscription (
152
- msg_type = JointState ,
153
- topic = "joint_states" ,
154
- callback = self .distance_to_goal .joint_state_callback ,
155
- qos_profile = QoSPresetProfiles .SENSOR_DATA .value ,
156
- )
157
-
158
144
# pylint: disable=attribute-defined-outside-init
159
145
# For attributes that are only used during the execution of the tree
160
146
# and get reset before the next execution, it is reasonable to define
@@ -237,12 +223,11 @@ def initialise(self) -> None:
237
223
self .tree_blackboard .motion_initial_distance = 0.0
238
224
self .tree_blackboard .motion_curr_distance = 0.0
239
225
240
- # Set the joint names
241
- self .distance_to_goal .set_joint_names (kinova .joint_names ())
226
+ # Store the trajectory to be able to monitor the robot's progress
227
+ self .traj = None
228
+ self .traj_i = 0
229
+ self .aligned_joint_indices = None
242
230
243
- # pylint: disable=too-many-branches, too-many-return-statements
244
- # This is the heart of the MoveTo behavior, so the number of branches
245
- # and return statements is reasonable.
246
231
def update (self ) -> py_trees .common .Status :
247
232
"""
248
233
Monitor the progress of moving the robot. This includes:
@@ -251,6 +236,11 @@ def update(self) -> py_trees.common.Status:
251
236
- Checking if motion is complete
252
237
- Updating feedback in the blackboard
253
238
"""
239
+
240
+ # pylint: disable=too-many-branches, too-many-return-statements
241
+ # This is the heart of the MoveTo behavior, so the number of branches
242
+ # and return statements is reasonable.
243
+
254
244
self .logger .info (f"{ self .name } [MoveTo::update()]" )
255
245
256
246
# Check the state of MoveIt
@@ -287,13 +277,13 @@ def update(self) -> py_trees.common.Status:
287
277
288
278
# Get the trajectory
289
279
with self .moveit2_lock :
290
- traj = self .moveit2 .get_trajectory (
280
+ self . traj = self .moveit2 .get_trajectory (
291
281
self .planning_future ,
292
282
cartesian = self .cartesian ,
293
283
cartesian_fraction_threshold = self .cartesian_fraction_threshold ,
294
284
)
295
- self .logger .info (f"Trajectory: { traj } | type { type ( traj ) } " )
296
- if traj is None :
285
+ self .logger .info (f"Trajectory: { self . traj } " )
286
+ if self . traj is None :
297
287
self .logger .error (
298
288
f"{ self .name } [MoveTo::update()] Failed to get trajectory from MoveIt!"
299
289
)
@@ -302,19 +292,19 @@ def update(self) -> py_trees.common.Status:
302
292
# MoveIt's default cartesian interpolator doesn't respect velocity
303
293
# scaling, so we need to manually add that.
304
294
if self .cartesian and self .moveit2 .max_velocity > 0.0 :
305
- MoveTo .scale_velocity (traj , self .moveit2 .max_velocity )
295
+ MoveTo .scale_velocity (self . traj , self .moveit2 .max_velocity )
306
296
307
297
# Set the trajectory's initial distance to goal
308
- self .tree_blackboard .motion_initial_distance = (
309
- self .distance_to_goal . set_trajectory ( traj )
298
+ self .tree_blackboard .motion_initial_distance = float (
299
+ len ( self .traj . points )
310
300
)
311
301
self .tree_blackboard .motion_curr_distance = (
312
302
self .tree_blackboard .motion_initial_distance
313
303
)
314
304
315
305
# Send the trajectory to MoveIt
316
306
with self .moveit2_lock :
317
- self .moveit2 .execute (traj )
307
+ self .moveit2 .execute (self . traj )
318
308
return py_trees .common .Status .RUNNING
319
309
320
310
# Still planning
@@ -351,9 +341,7 @@ def update(self) -> py_trees.common.Status:
351
341
self .tree_blackboard .motion_curr_distance = 0.0
352
342
return py_trees .common .Status .SUCCESS
353
343
if self .motion_future is not None :
354
- self .tree_blackboard .motion_curr_distance = (
355
- self .distance_to_goal .get_distance ()
356
- )
344
+ self .tree_blackboard .motion_curr_distance = self .get_distance_to_goal ()
357
345
if self .motion_future .done ():
358
346
# The goal has finished executing
359
347
if self .motion_future .result ().status == GoalStatus .STATUS_SUCCEEDED :
@@ -394,7 +382,7 @@ def terminate(self, new_status: py_trees.common.Status) -> None:
394
382
# A termination request has not succeeded until the MoveIt2 action server is IDLE
395
383
with self .moveit2_lock :
396
384
while self .moveit2 .query_state () != MoveIt2State .IDLE :
397
- self .node .get_logger () .info (
385
+ self .node .logger .info (
398
386
f"MoveTo Update MoveIt2State not Idle { time .time ()} { terminate_requested_time } "
399
387
f"{ self .terminate_timeout_s } "
400
388
)
@@ -451,88 +439,6 @@ def scale_velocity(traj: JointTrajectory, scale_factor: float) -> None:
451
439
for i in range (len (point .accelerations )):
452
440
point .accelerations [i ] *= scale_factor ** 2
453
441
454
-
455
- class DistanceToGoal :
456
- """
457
- The DistanceToGoal class is used to determine how much of the trajectory
458
- the robot arm has yet to execute.
459
-
460
- In practice, it keeps track of what joint state along the trajectory the
461
- robot is currently in, and returns the number of remaining joint states. As
462
- a result, this is not technically a measure of either distance or time, but
463
- should give some intuition of how much of the trajectory is left to execute.
464
- """
465
-
466
- def __init__ (self ):
467
- """
468
- Initializes the DistanceToGoal class.
469
- """
470
- self .joint_names = None
471
- self .aligned_joint_indices = None
472
-
473
- self .trajectory = None
474
- self .curr_joint_state_i = 0
475
- self .curr_joint_state = None
476
-
477
- def set_joint_names (self , joint_names : List [str ]) -> None :
478
- """
479
- This function stores the robot's joint names.
480
-
481
- Parameters
482
- ----------
483
- joint_names: The names of the joints that the robot arm is moving.
484
- """
485
- self .joint_names = joint_names
486
-
487
- def set_trajectory (self , trajectory : JointTrajectory ) -> float :
488
- """
489
- This function takes in the robot's trajectory and returns the initial
490
- distance to goal e.g., the distance between the starting and ending
491
- joint state. In practice, this returns the length of the trajectory.
492
- """
493
- self .trajectory = trajectory
494
- self .curr_joint_state_i = 0
495
- return float (len (self .trajectory .points ))
496
-
497
- def joint_state_callback (self , msg : JointState ) -> None :
498
- """
499
- This function stores the robot's current joint state, and aligns those
500
- messages with the order of joints in the robot's computed trajectory.
501
-
502
- It assumes:
503
- (a) that there are joint state messages that contain all of the robot's
504
- joints that are in the trajectory.
505
- (b) that the order of joins in the trajectory and in the joint state
506
- messages never changes.
507
- """
508
- # Only store messages that have all of the robot's joints. This is
509
- # because sometimes nodes other than the robot will be publishing to
510
- # the joint state publisher.
511
- if self .joint_names is None :
512
- return
513
- contains_all_robot_joints = True
514
- for joint_name in self .joint_names :
515
- if joint_name not in msg .name :
516
- contains_all_robot_joints = False
517
- break
518
- if contains_all_robot_joints :
519
- self .curr_joint_state = msg
520
-
521
- # Align the joint names between the JointState and JointTrajectory
522
- # messages.
523
- if self .aligned_joint_indices is None and self .trajectory is not None :
524
- self .aligned_joint_indices = []
525
- for joint_name in self .joint_names :
526
- if (
527
- joint_name in msg .name
528
- and joint_name in self .trajectory .joint_names
529
- ):
530
- joint_state_i = msg .name .index (joint_name )
531
- joint_traj_i = self .trajectory .joint_names .index (joint_name )
532
- self .aligned_joint_indices .append (
533
- (joint_name , joint_state_i , joint_traj_i )
534
- )
535
-
536
442
@staticmethod
537
443
def joint_position_dist (point1 : float , point2 : float ) -> float :
538
444
"""
@@ -542,44 +448,63 @@ def joint_position_dist(point1: float, point2: float) -> float:
542
448
abs_dist = abs (point1 - point2 ) % (2 * math .pi )
543
449
return min (abs_dist , 2 * math .pi - abs_dist )
544
450
545
- def get_distance (self ) -> Optional [ float ] :
451
+ def get_distance_to_goal (self ) -> float :
546
452
"""
547
- This function determines where in the trajectory the robot is. It does
548
- this by computing the distance (L1 distance across the joint positions)
549
- between the current joint state and the upcoming joint states in the
550
- trajectory, and selecting the nearest local min.
453
+ Returns the remaining distance to the goal.
551
454
552
- This function assumes the joint names are aligned between the JointState
553
- and JointTrajectory messages.
455
+ In practice, it keeps track of what joint state along the trajectory the
456
+ robot is currently in, and returns the number of remaining joint states. As
457
+ a result, this is not technically a measure of either distance or time, but
458
+ should give some intuition of how much of the trajectory is left to execute.
554
459
"""
555
- # If we haven't yet received a joint state message to the trajectory,
556
- # immediately return
460
+ # If the trajectory has not been set, something is wrong.
461
+ if self .traj is None :
462
+ self .logger .error (
463
+ f"{ self .name } [MoveTo::get_distance_to_goal()] Trajectory is None!"
464
+ )
465
+ return 0.0
466
+
467
+ # Get the latest joint state of the robot
468
+ with self .moveit2_lock :
469
+ curr_joint_state = self .moveit2 .joint_state
470
+
471
+ # Lazily align the joints between the trajectory and the joint states message
557
472
if self .aligned_joint_indices is None :
558
- if self .trajectory is None :
559
- return None
560
- return float (len (self .trajectory .points ) - self .curr_joint_state_i )
473
+ self .aligned_joint_indices = []
474
+ for joint_traj_i , joint_name in enumerate (self .traj .joint_names ):
475
+ if joint_name in curr_joint_state .name :
476
+ joint_state_i = curr_joint_state .name .index (joint_name )
477
+ self .aligned_joint_indices .append (
478
+ (joint_name , joint_state_i , joint_traj_i )
479
+ )
480
+ else :
481
+ self .logger .error (
482
+ f"{ self .name } [MoveTo::get_distance_to_goal()] Joint { joint_name } not in "
483
+ "current joint state, despite being in the trajectory. Skipping this joint "
484
+ "in distance to goal calculation."
485
+ )
561
486
562
- # Else, determine how much remaining the robot has of the trajectory
563
- prev_dist = None
564
- for i in range (self .curr_joint_state_i , len (self .trajectory .points )):
487
+ # Get the point along the trajectory closest to the robot's current joint state
488
+ min_dist = None
489
+ for i in range (self .traj_i , len (self .traj .points )):
565
490
# Compute the distance between the current joint state and the
566
491
# ujoint state at index i.
567
- traj_joint_state = self .trajectory .points [i ]
492
+ traj_joint_state = self .traj .points [i ]
568
493
dist = sum (
569
- DistanceToGoal .joint_position_dist (
570
- self . curr_joint_state .position [joint_state_i ],
494
+ MoveTo .joint_position_dist (
495
+ curr_joint_state .position [joint_state_i ],
571
496
traj_joint_state .positions [joint_traj_i ],
572
497
)
573
498
for (_ , joint_state_i , joint_traj_i ) in self .aligned_joint_indices
574
499
)
575
500
576
501
# If the distance is increasing, we've found the local min.
577
- if prev_dist is not None :
578
- if dist >= prev_dist :
579
- self .curr_joint_state_i = i - 1
580
- return float (len (self .trajectory .points ) - self .curr_joint_state_i )
502
+ if min_dist is not None :
503
+ if dist >= min_dist :
504
+ self .traj_i = i - 1
505
+ return float (len (self .traj .points ) - self .traj_i )
581
506
582
- prev_dist = dist
507
+ min_dist = dist
583
508
584
509
# If the distance never increased, we are nearing the final waypoint.
585
510
# Because the robot may still have slight motion even after this point,
0 commit comments