Skip to content

Commit e340ca0

Browse files
authored
Make MoveTo use MoveIt2's joint state subscriber (#97)
make MoveTo use MoveIt2's joint state subscriber
1 parent 16bb3f6 commit e340ca0

File tree

1 file changed

+60
-135
lines changed

1 file changed

+60
-135
lines changed

ada_feeding/ada_feeding/behaviors/move_to.py

Lines changed: 60 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,13 @@
77
# Standard imports
88
import math
99
import time
10-
from typing import List, Optional
1110

1211
# Third-party imports
1312
from action_msgs.msg import GoalStatus
1413
from moveit_msgs.msg import MoveItErrorCodes
1514
import py_trees
1615
from pymoveit2 import MoveIt2State
17-
from pymoveit2.robots import kinova
1816
from rclpy.node import Node
19-
from rclpy.qos import QoSPresetProfiles
20-
from sensor_msgs.msg import JointState
2117
from trajectory_msgs.msg import JointTrajectory
2218

2319
# Local imports
@@ -145,16 +141,6 @@ def __init__(
145141
self.node,
146142
)
147143

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-
158144
# pylint: disable=attribute-defined-outside-init
159145
# For attributes that are only used during the execution of the tree
160146
# and get reset before the next execution, it is reasonable to define
@@ -237,12 +223,11 @@ def initialise(self) -> None:
237223
self.tree_blackboard.motion_initial_distance = 0.0
238224
self.tree_blackboard.motion_curr_distance = 0.0
239225

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
242230

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.
246231
def update(self) -> py_trees.common.Status:
247232
"""
248233
Monitor the progress of moving the robot. This includes:
@@ -251,6 +236,11 @@ def update(self) -> py_trees.common.Status:
251236
- Checking if motion is complete
252237
- Updating feedback in the blackboard
253238
"""
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+
254244
self.logger.info(f"{self.name} [MoveTo::update()]")
255245

256246
# Check the state of MoveIt
@@ -287,13 +277,13 @@ def update(self) -> py_trees.common.Status:
287277

288278
# Get the trajectory
289279
with self.moveit2_lock:
290-
traj = self.moveit2.get_trajectory(
280+
self.traj = self.moveit2.get_trajectory(
291281
self.planning_future,
292282
cartesian=self.cartesian,
293283
cartesian_fraction_threshold=self.cartesian_fraction_threshold,
294284
)
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:
297287
self.logger.error(
298288
f"{self.name} [MoveTo::update()] Failed to get trajectory from MoveIt!"
299289
)
@@ -302,19 +292,19 @@ def update(self) -> py_trees.common.Status:
302292
# MoveIt's default cartesian interpolator doesn't respect velocity
303293
# scaling, so we need to manually add that.
304294
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)
306296

307297
# 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)
310300
)
311301
self.tree_blackboard.motion_curr_distance = (
312302
self.tree_blackboard.motion_initial_distance
313303
)
314304

315305
# Send the trajectory to MoveIt
316306
with self.moveit2_lock:
317-
self.moveit2.execute(traj)
307+
self.moveit2.execute(self.traj)
318308
return py_trees.common.Status.RUNNING
319309

320310
# Still planning
@@ -351,9 +341,7 @@ def update(self) -> py_trees.common.Status:
351341
self.tree_blackboard.motion_curr_distance = 0.0
352342
return py_trees.common.Status.SUCCESS
353343
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()
357345
if self.motion_future.done():
358346
# The goal has finished executing
359347
if self.motion_future.result().status == GoalStatus.STATUS_SUCCEEDED:
@@ -394,7 +382,7 @@ def terminate(self, new_status: py_trees.common.Status) -> None:
394382
# A termination request has not succeeded until the MoveIt2 action server is IDLE
395383
with self.moveit2_lock:
396384
while self.moveit2.query_state() != MoveIt2State.IDLE:
397-
self.node.get_logger().info(
385+
self.node.logger.info(
398386
f"MoveTo Update MoveIt2State not Idle {time.time()} {terminate_requested_time} "
399387
f"{self.terminate_timeout_s}"
400388
)
@@ -451,88 +439,6 @@ def scale_velocity(traj: JointTrajectory, scale_factor: float) -> None:
451439
for i in range(len(point.accelerations)):
452440
point.accelerations[i] *= scale_factor**2
453441

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-
536442
@staticmethod
537443
def joint_position_dist(point1: float, point2: float) -> float:
538444
"""
@@ -542,44 +448,63 @@ def joint_position_dist(point1: float, point2: float) -> float:
542448
abs_dist = abs(point1 - point2) % (2 * math.pi)
543449
return min(abs_dist, 2 * math.pi - abs_dist)
544450

545-
def get_distance(self) -> Optional[float]:
451+
def get_distance_to_goal(self) -> float:
546452
"""
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.
551454
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.
554459
"""
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
557472
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+
)
561486

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)):
565490
# Compute the distance between the current joint state and the
566491
# ujoint state at index i.
567-
traj_joint_state = self.trajectory.points[i]
492+
traj_joint_state = self.traj.points[i]
568493
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],
571496
traj_joint_state.positions[joint_traj_i],
572497
)
573498
for (_, joint_state_i, joint_traj_i) in self.aligned_joint_indices
574499
)
575500

576501
# 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)
581506

582-
prev_dist = dist
507+
min_dist = dist
583508

584509
# If the distance never increased, we are nearing the final waypoint.
585510
# Because the robot may still have slight motion even after this point,

0 commit comments

Comments
 (0)