Esempio n. 1
0
    def _init_moveit(self):
        """
        Initialize moveit and setup move_group object
        """
        self.moveit_group = MoveGroup(self.configs.ARM.MOVEGROUP_NAME,
                                      self.configs.ARM.ARM_BASE_FRAME,
                                      self.configs.ARM.EE_FRAME,
                                      self.configs.ARM.ROSSRV_CART_PATH,
                                      listener=self.tf_listener,
                                      plan_only=False)

        self.moveit_group.setPlannerId(self.moveit_planner)
        self.moveit_group.setPlanningTime(self.planning_time)
Esempio n. 2
0
class Arm(object):
    """
    This is a parent class on which the robot
    specific Arm classes would be built.
    """

    __metaclass__ = ABCMeta

    def __init__(
        self,
        configs,
        moveit_planner="ESTkConfigDefault",
        planning_time=30,
        analytical_ik=None,
        use_moveit=True,
    ):
        """
        Constructor for Arm parent class.

        :param configs: configurations for arm
        :param moveit_planner: moveit planner type
        :param analytical_ik: customized analytical ik class
                              if you have one, None otherwise
        :param use_moveit: use moveit or not, default is True

        :type configs: YACS CfgNode
        :type moveit_planner: string
        :type analytical_ik: None or an Analytical ik class
        :type use_moveit: bool
        """
        self.configs = configs
        self.moveit_planner = moveit_planner
        self.planning_time = planning_time
        self.moveit_group = None
        self.use_moveit = use_moveit

        self.joint_state_lock = threading.RLock()
        self.tf_listener = tf.TransformListener()
        if self.use_moveit:
            self._init_moveit()

        if analytical_ik is not None:
            self.ana_ik_solver = analytical_ik(
                configs.ARM.ARM_BASE_FRAME, configs.ARM.EE_FRAME
            )

        self.arm_joint_names = self.configs.ARM.JOINT_NAMES
        self.arm_dof = len(self.arm_joint_names)

        # Subscribers
        self._joint_angles = dict()
        self._joint_velocities = dict()
        self._joint_efforts = dict()
        rospy.Subscriber(
            configs.ARM.ROSTOPIC_JOINT_STATES, JointState, self._callback_joint_states
        )

        # Ros-Params
        rospy.set_param("pyrobot/base_link", configs.ARM.ARM_BASE_FRAME)
        rospy.set_param("pyrobot/gripper_link", configs.ARM.EE_FRAME)
        rospy.set_param(
            "pyrobot/robot_description", configs.ARM.ARM_ROBOT_DSP_PARAM_NAME
        )

        # Publishers
        self.joint_pub = None
        self._setup_joint_pub()

        # Services
        self._ik_service = rospy.ServiceProxy("pyrobot/ik", IkCommand)
        try:
            self._ik_service.wait_for_service(timeout=3)
        except:
            rospy.logerr("Timeout waiting for Inverse Kinematics Service!!")

        self._fk_service = rospy.ServiceProxy("pyrobot/fk", FkCommand)
        try:
            self._fk_service.wait_for_service(timeout=3)
        except:
            rospy.logerr("Timeout waiting for Forward Kinematics Service!!")

    @abstractmethod
    def go_home(self):
        """
        Reset robot to default home position
        """
        pass

    @property
    def pose_ee(self):
        """
        Return the end effector pose w.r.t 'ARM_BASE_FRAME'

        :return:
                trans: translational vector (shape: :math:`[3, 1]`)

                rot_mat: rotational matrix (shape: :math:`[3, 3]`)

                quat: rotational matrix in the form
                of quaternion (shape: :math:`[4,]`)

        :rtype: tuple (trans, rot_mat, quat)
        """
        return self.get_ee_pose(base_frame=self.configs.ARM.ARM_BASE_FRAME)

    def get_ee_pose(self, base_frame):
        """
        Return the end effector pose with respect to the base_frame

        :param base_frame: reference frame
        :type base_frame: string
        :return:
                tuple (trans, rot_mat, quat)

                trans: translational vector (shape: :math:`[3, 1]`)

                rot_mat: rotational matrix (shape: :math:`[3, 3]`)

                quat: rotational matrix in the form
                of quaternion (shape: :math:`[4,]`)

        :rtype: tuple or ROS PoseStamped
        """
        return self.get_transform(base_frame, self.configs.ARM.EE_FRAME)

    def get_transform(self, src_frame, dest_frame):
        """
        Return the transform from the src_frame to dest_frame

        :param src_frame: source frame
        :param dest_frame: destination frame
        :type src_frame: string
        :type dest_frame: basestring
        :return:
               tuple (trans, rot_mat, quat )

               trans: translational vector (shape: :math:`[3, 1]`)

               rot_mat: rotational matrix (shape: :math:`[3, 3]`)

               quat: rotational matrix in the form
               of quaternion (shape: :math:`[4,]`)

        :rtype: tuple or ROS PoseStamped
        """
        trans, quat = prutil.get_tf_transform(self.tf_listener, src_frame, dest_frame)
        rot_mat = prutil.quat_to_rot_mat(quat)
        trans = np.array(trans).reshape(-1, 1)
        rot_mat = np.array(rot_mat)
        quat = np.array(quat)
        return trans, rot_mat, quat

    def get_joint_angles(self):
        """
        Return the joint angles

        :return: joint_angles
        :rtype: np.ndarray
        """
        self.joint_state_lock.acquire()
        joint_angles = []
        for joint in self.arm_joint_names:
            joint_angles.append(self.get_joint_angle(joint))
        joint_angles = np.array(joint_angles).flatten()
        self.joint_state_lock.release()
        return joint_angles

    def get_joint_velocities(self):
        """
        Return the joint velocities

        :return: joint_vels
        :rtype: np.ndarray
        """
        self.joint_state_lock.acquire()
        joint_vels = []
        for joint in self.arm_joint_names:
            joint_vels.append(self.get_joint_velocity(joint))
        joint_vels = np.array(joint_vels).flatten()
        self.joint_state_lock.release()
        return joint_vels

    def get_joint_torques(self):
        """
        Return the joint torques

        :return: joint_torques
        :rtype: np.ndarray
        """
        self.joint_state_lock.acquire()
        joint_torques = []
        for joint in self.arm_joint_names:
            try:
                joint_torques.append(self.get_joint_torque(joint))
            except (ValueError, IndexError):
                rospy.loginfo("Torque value for joint " "[%s] not available!" % joint)
        joint_torques = np.array(joint_torques).flatten()
        self.joint_state_lock.release()
        return joint_torques

    def get_joint_angle(self, joint):
        """
        Return the joint angle of the 'joint'

        :param joint: joint name
        :type joint: string
        :return: joint angle
        :rtype: float
        """
        if joint not in self.arm_joint_names:
            raise ValueError("%s not in arm joint list!" % joint)
        if joint not in self._joint_angles.keys():
            raise ValueError("Joint angle for joint $s not available!" % joint)
        return self._joint_angles[joint]

    def get_joint_velocity(self, joint):
        """
        Return the joint velocity of the 'joint'

        :param joint: joint name
        :type joint: string
        :return: joint velocity
        :rtype: float
        """
        if joint not in self.arm_joint_names:
            raise ValueError("%s not in arm joint list!" % joint)
        if joint not in self._joint_velocities.keys():
            raise ValueError("Joint velocity for joint" " $s not available!" % joint)
        return self._joint_velocities[joint]

    def get_joint_torque(self, joint):
        """
        Return the joint torque of the 'joint'

        :param joint: joint name
        :type joint: string
        :return: joint torque
        :rtype: float
        """
        if joint not in self.arm_joint_names:
            raise ValueError("%s not in arm joint list!" % joint)
        if joint not in self._joint_efforts.keys():
            raise ValueError("Joint torque for joint $s" " not available!" % joint)
        return self._joint_efforts[joint]

    def set_joint_positions(self, positions, plan=True, wait=True, **kwargs):
        """
        Sets the desired joint angles for all arm joints

        :param positions: list of length #of joints, angles in radians
        :param plan: whether to use moveit to plan a path. Without planning,
                     there is no collision checking and each joint will
                     move to its target joint position directly.
        :param wait: if True, it will wait until the desired
                     joint positions are achieved
        :type positions: list
        :type plan: bool
        :type wait: bool

        :return: True if successful; False otherwise (timeout, etc.)
        :rtype: bool
        """
        result = False
        if isinstance(positions, np.ndarray):
            positions = positions.flatten().tolist()
        if plan:
            if not self.use_moveit:
                raise ValueError(
                    "Moveit is not initialized, " "did you pass in use_moveit=True?"
                )
            if isinstance(positions, np.ndarray):
                positions = positions.tolist()

            rospy.loginfo("Moveit Motion Planning...")
            result = self.moveit_group.moveToJointPosition(
                self.arm_joint_names, positions, wait=wait
            )
        else:
            self._pub_joint_positions(positions)
            if wait:
                result = self._loop_angle_pub_cmd(self._pub_joint_positions, positions)
        return result

    def make_plan_joint_positions(self, positions, **kwargs):
        result = None
        if isinstance(positions, np.ndarray):
            positions = positions.flatten().tolist()

        if not self.use_moveit:
            raise ValueError(
                "Moveit is not initialized, " "did you pass in use_moveit=True?"
            )

        rospy.loginfo("Moveit Motion Planning...")
        result = self.moveit_group.motionPlanToJointPosition(
            self.arm_joint_names, positions
        )
        return [p.positions for p in result]

    def set_joint_velocities(self, velocities, **kwargs):
        """
        Sets the desired joint velocities for all arm joints

        :param velocities: target joint velocities
        :type velocities: list
        """
        self._pub_joint_velocities(velocities)

    def set_joint_torques(self, torques, **kwargs):
        """
        Sets the desired joint torques for all arm joints

        :param torques: target joint torques
        :type torques: list
        """
        self._pub_joint_torques(torques)

    def set_ee_pose(
        self, position, orientation, plan=True, wait=True, numerical=True, **kwargs
    ):
        """
        Commands robot arm to desired end-effector pose
        (w.r.t. 'ARM_BASE_FRAME').
        Computes IK solution in joint space and calls set_joint_positions.
        Will wait for command to complete if wait is set to True.

        :param position: position of the end effector (shape: :math:`[3,]`)
        :param orientation: orientation of the end effector
                            (can be rotation matrix, euler angles (yaw,
                            pitch, roll), or quaternion)
                            (shape: :math:`[3, 3]`, :math:`[3,]`
                            or :math:`[4,]`)
                            The convention of the Euler angles here
                            is z-y'-x' (intrinsic rotations),
                            which is one type of Tait-Bryan angles.
        :param plan: use moveit the plan a path to move to the desired pose
        :param wait: wait until the desired pose is achieved
        :param numerical: use numerical inverse kinematics solver or
                          analytical inverse kinematics solver
        :type position: np.ndarray
        :type orientation: np.ndarray
        :type plan: bool
        :type wait: bool
        :type numerical: bool
        :return: Returns True if command succeeded, False otherwise
        :rtype: bool
        """
        if plan:
            if not self.use_moveit:
                raise ValueError(
                    "Using plan=True when moveit is not"
                    " initialized, did you pass "
                    "in use_moveit=True?"
                )
            pose = Pose()
            position = position.flatten()
            if orientation.size == 4:
                orientation = orientation.flatten()
                ori_x = orientation[0]
                ori_y = orientation[1]
                ori_z = orientation[2]
                ori_w = orientation[3]
            elif orientation.size == 3:
                quat = prutil.euler_to_quat(orientation)
                ori_x = quat[0]
                ori_y = quat[1]
                ori_z = quat[2]
                ori_w = quat[3]
            elif orientation.size == 9:
                quat = prutil.rot_mat_to_quat(orientation)
                ori_x = quat[0]
                ori_y = quat[1]
                ori_z = quat[2]
                ori_w = quat[3]
            else:
                raise TypeError(
                    "Orientation must be in one "
                    "of the following forms:"
                    "rotation matrix, euler angles, or quaternion"
                )
            pose.position.x, pose.position.y, pose.position.z = (
                position[0],
                position[1],
                position[2],
            )
            (
                pose.orientation.x,
                pose.orientation.y,
                pose.orientation.z,
                pose.orientation.w,
            ) = (ori_x, ori_y, ori_z, ori_w)
            result = self.moveit_group.moveToPose(pose, wait=True)
        else:
            joint_positions = self.compute_ik(
                position, orientation, numerical=numerical
            )
            result = False
            if joint_positions is None:
                rospy.logerr("No IK solution found; check if target_pose is valid")
            else:
                result = self.set_joint_positions(joint_positions, plan=plan, wait=wait)
        return result

    def make_plan_pose(
        self, position, orientation, wait=True, numerical=True, **kwargs
    ):

        if not self.use_moveit:
            raise ValueError(
                "Using plan=True when moveit is not"
                " initialized, did you pass "
                "in use_moveit=True?"
            )
        pose = Pose()
        position = position.flatten()
        if orientation.size == 4:
            orientation = orientation.flatten()
            ori_x = orientation[0]
            ori_y = orientation[1]
            ori_z = orientation[2]
            ori_w = orientation[3]
        elif orientation.size == 3:
            quat = prutil.euler_to_quat(orientation)
            ori_x = quat[0]
            ori_y = quat[1]
            ori_z = quat[2]
            ori_w = quat[3]
        elif orientation.size == 9:
            quat = prutil.rot_mat_to_quat(orientation)
            ori_x = quat[0]
            ori_y = quat[1]
            ori_z = quat[2]
            ori_w = quat[3]
        else:
            raise TypeError(
                "Orientation must be in one "
                "of the following forms:"
                "rotation matrix, euler angles, or quaternion"
            )
        pose.position.x, pose.position.y, pose.position.z = (
            position[0],
            position[1],
            position[2],
        )
        (
            pose.orientation.x,
            pose.orientation.y,
            pose.orientation.z,
            pose.orientation.w,
        ) = (ori_x, ori_y, ori_z, ori_w)
        result = self.moveit_group.motionPlanToPose(pose, wait=True)

        return [p.positions for p in result]

    def move_ee_xyz(
        self, displacement, eef_step=0.005, numerical=True, plan=True, **kwargs
    ):
        """
        Keep the current orientation fixed, move the end
        effector in {xyz} directions

        :param displacement: (delta_x, delta_y, delta_z)
        :param eef_step: resolution (m) of the interpolation
                         on the cartesian path
        :param numerical: use numerical inverse kinematics solver or
                          analytical inverse kinematics solver
        :param plan: use moveit the plan a path to move to the
                     desired pose. If False,
                     it will do linear interpolation along the path,
                     and simply use IK solver to find the
                     sequence of desired joint positions and
                     then call `set_joint_positions`
        :type displacement: np.ndarray
        :type eef_step: float
        :type numerical: bool
        :type plan: bool
        :return: whether the movement is successful or not
        :rtype: bool
        """
        displacement = displacement.reshape(-1, 1)

        path_len = np.linalg.norm(displacement)
        num_pts = int(np.ceil(path_len / float(eef_step)))
        if num_pts <= 1:
            num_pts = 2
        if (hasattr(self, "ana_ik_solver") and not numerical) or not plan:
            ee_pose = self.get_ee_pose(self.configs.ARM.ARM_BASE_FRAME)
            cur_pos, cur_ori, cur_quat = ee_pose
            waypoints_sp = np.linspace(0, path_len, num_pts)
            waypoints = cur_pos + waypoints_sp / float(path_len) * displacement
            way_joint_positions = []
            qinit = self.get_joint_angles().tolist()
            for i in range(waypoints.shape[1]):
                joint_positions = self.compute_ik(
                    waypoints[:, i].flatten(),
                    cur_quat,
                    qinit=qinit,
                    numerical=numerical,
                )
                if joint_positions is None:
                    rospy.logerr(
                        "No IK solution found; " "check if target_pose is valid"
                    )
                    return False
                way_joint_positions.append(copy.deepcopy(joint_positions))
                qinit = copy.deepcopy(joint_positions)
            success = False
            for joint_positions in way_joint_positions:
                success = self.set_joint_positions(
                    joint_positions, plan=plan, wait=True
                )

            return success
        else:
            if not self.use_moveit:
                raise ValueError(
                    "Using plan=True when moveit is not"
                    " initialized, did you pass "
                    "in use_moveit=True?"
                )

            ee_pose = self.get_ee_pose(self.configs.ARM.ARM_BASE_FRAME)
            cur_pos, cur_ori, cur_quat = ee_pose
            cur_pos = np.array(cur_pos).reshape(-1, 1)
            cur_quat = np.array(cur_quat)

            waypoints_sp = np.linspace(0, path_len, num_pts)
            waypoints = cur_pos + waypoints_sp / path_len * displacement
            moveit_waypoints = []
            wpose = Pose()
            for i in range(waypoints.shape[1]):
                if i < 1:
                    continue
                wpose.position.x = waypoints[0, i]
                wpose.position.y = waypoints[1, i]
                wpose.position.z = waypoints[2, i]
                wpose.orientation.x = cur_quat[0]
                wpose.orientation.y = cur_quat[1]
                wpose.orientation.z = cur_quat[2]
                wpose.orientation.w = cur_quat[3]
                moveit_waypoints.append(copy.deepcopy(wpose))

            result = self.moveit_group.followCartesian(
                way_points=moveit_waypoints,  # waypoints to follow
                way_point_frame=self.configs.ARM.ARM_BASE_FRAME,
                max_step=eef_step,  # eef_step
                jump_threshold=0.0,
            )  # jump_threshold

            if result is False:
                return False

            ee_pose = self.get_ee_pose(self.configs.ARM.ARM_BASE_FRAME)
            cur_pos, cur_ori, cur_quat = ee_pose

            cur_pos = np.array(cur_pos).reshape(-1, 1)

            success = True
            diff = cur_pos.flatten() - waypoints[:, -1].flatten()
            error = np.linalg.norm(diff)
            if error > self.configs.ARM.MAX_EE_ERROR:
                rospy.logerr("Move end effector along xyz failed!")
                success = False
            return success

    def compute_fk_position(self, joint_positions, des_frame):
        """
        Given joint angles, compute the pose of desired_frame with respect
        to the base frame (self.configs.ARM.ARM_BASE_FRAME). The desired frame
        must be in self.arm_link_names

        :param joint_positions: joint angles
        :param des_frame: desired frame
        :type joint_positions: np.ndarray
        :type des_frame: string
        :return: translational vector and rotational matrix
        :rtype: np.ndarray, np.ndarray
        """
        joint_positions = joint_positions.flatten()

        req = FkCommandRequest()
        req.joint_angles = list(joint_positions)
        req.end_frame = des_frame
        try:
            resp = self._fk_service(req)
        except:
            rospy.logerr("FK Service call failed")
            resp = FkCommandResponse()
            resp.success = False

        if not resp.success:
            return None

        pos = np.asarray(resp.pos).reshape(3, 1)
        rot = prutil.quat_to_rot_mat(resp.quat)
        return pos, rot

    def get_jacobian(self, joint_angles):
        """
        Return the geometric jacobian on the given joint angles.
        Refer to P112 in "Robotics: Modeling, Planning, and Control"

        :param joint_angles: joint_angles
        :type joint_angles: list or flattened np.ndarray
        :return: jacobian
        :rtype: np.ndarray
        """
        raise NotImplementedError

    def compute_fk_velocity(self, joint_positions, joint_velocities, des_frame):
        """
        Given joint_positions and joint velocities,
        compute the velocities of des_frame with respect
        to the base frame

        :param joint_positions: joint positions
        :param joint_velocities: joint velocities
        :param des_frame: end frame
        :type joint_positions: np.ndarray
        :type joint_velocities: np.ndarray
        :type des_frame: string
        :return: translational and rotational
                 velocities (vx, vy, vz, wx, wy, wz)
        :rtype: np.ndarray
        """
        raise NotImplementedError

    def compute_ik(self, position, orientation, qinit=None, numerical=True):
        """
        Inverse kinematics

        :param position: end effector position (shape: :math:`[3,]`)
        :param orientation: end effector orientation.
                            It can be quaternion ([x,y,z,w],
                            shape: :math:`[4,]`),
                            euler angles (yaw, pitch, roll
                            angles (shape: :math:`[3,]`)),
                            or rotation matrix (shape: :math:`[3, 3]`)
        :param qinit: initial joint positions for numerical IK
        :param numerical: use numerical IK or analytical IK
        :type position: np.ndarray
        :type orientation: np.ndarray
        :type qinit: list
        :type numerical: bool
        :return: None or joint positions
        :rtype: np.ndarray
        """
        position = position.flatten()
        if orientation.size == 4:
            orientation = orientation.flatten()
            ori_x = orientation[0]
            ori_y = orientation[1]
            ori_z = orientation[2]
            ori_w = orientation[3]
        elif orientation.size == 3:
            quat = prutil.euler_to_quat(orientation)
            ori_x = quat[0]
            ori_y = quat[1]
            ori_z = quat[2]
            ori_w = quat[3]
        elif orientation.size == 9:
            quat = prutil.rot_mat_to_quat(orientation)
            ori_x = quat[0]
            ori_y = quat[1]
            ori_z = quat[2]
            ori_w = quat[3]
        else:
            raise TypeError(
                "Orientation must be in one "
                "of the following forms:"
                "rotation matrix, euler angles, or quaternion"
            )
        if qinit is None:
            qinit = self.get_joint_angles().tolist()
        elif isinstance(qinit, np.ndarray):
            qinit = qinit.flatten().tolist()
        if numerical:
            pos_tol = self.configs.ARM.IK_POSITION_TOLERANCE
            ori_tol = self.configs.ARM.IK_ORIENTATION_TOLERANCE

            req = IkCommandRequest()
            req.init_joint_positions = qinit
            req.pose = [
                position[0],
                position[1],
                position[2],
                ori_x,
                ori_y,
                ori_z,
                ori_w,
            ]
            req.tolerance = 3 * [pos_tol] + 3 * [ori_tol]

            try:
                resp = self._ik_service(req)
            except:
                rospy.logerr("IK Service call failed")
                resp = IkCommandResponse()
                resp.success = False

            if not resp.success:
                joint_positions = None
            else:
                joint_positions = resp.joint_positions
        else:
            if not hasattr(self, "ana_ik_solver"):
                raise TypeError(
                    "Analytical solver not provided, "
                    "please use `numerical=True`"
                    "to use the numerical method "
                    "for inverse kinematics"
                )
            else:
                joint_positions = self.ana_ik_solver.get_ik(
                    qinit,
                    position[0],
                    position[1],
                    position[2],
                    ori_x,
                    ori_y,
                    ori_z,
                    ori_w,
                )
        if joint_positions is None:
            return None
        print(joint_positions)
        return np.array(joint_positions)

    def _callback_joint_states(self, msg):
        """
        ROS subscriber callback for arm joint state (position, velocity)

        :param msg: Contains message published in topic
        :type msg: sensor_msgs/JointState
        """
        self.joint_state_lock.acquire()
        for idx, name in enumerate(msg.name):
            if name in self.arm_joint_names:
                if idx < len(msg.position):
                    self._joint_angles[name] = msg.position[idx]
                if idx < len(msg.velocity):
                    self._joint_velocities[name] = msg.velocity[idx]
                if idx < len(msg.effort):
                    self._joint_efforts[name] = msg.effort[idx]
        self.joint_state_lock.release()

    def _pub_joint_positions(self, positions):
        joint_state = JointState()
        joint_state.position = tuple(positions)
        self.joint_pub.publish(joint_state)

    def _pub_joint_velocities(self, velocities):
        joint_state = JointState()
        joint_state.velocity = tuple(velocities)
        self.joint_pub.publish(joint_state)

    def _pub_joint_torques(self, torques):
        joint_state = JointState()
        joint_state.effort = tuple(torques)
        self.joint_pub.publish(joint_state)

    def _init_moveit(self):
        """
        Initialize moveit and setup move_group object
        """
        self.moveit_group = MoveGroup(
            self.configs.ARM.MOVEGROUP_NAME,
            self.configs.ARM.ARM_BASE_FRAME,
            self.configs.ARM.EE_FRAME,
            self.configs.ARM.ROSSRV_CART_PATH,
            self.configs.ARM.ROSSRV_MP_PATH,
            listener=self.tf_listener,
            plan_only=False,
        )

        self.moveit_group.setPlannerId(self.moveit_planner)
        self.moveit_group.setPlanningTime(self.planning_time)

    def _angle_error_is_small(self, target_joints):
        cur_joint_vals = self.get_joint_angles()
        joint_diff = cur_joint_vals - np.array(target_joints)
        error = np.max(np.abs(joint_diff))
        if error <= self.configs.ARM.MAX_JOINT_ERROR:
            return joint_diff, error, True
        else:
            return joint_diff, error, False

    def _loop_angle_pub_cmd(self, cmd, joint_vals):
        start_time = time.time()
        vel_zero_times = 0
        # wait 10s in worse case
        success = False
        for i in range(int(10 / self.configs.ARM.WAIT_MIN_TIME)):
            cmd(joint_vals)
            res = self._angle_error_is_small(joint_vals)
            joint_diff, error, eflag = res
            if eflag:
                success = True
                break
            vels = self.get_joint_velocities()
            es_time = time.time() - start_time
            if es_time > 0.5 and np.max(np.abs(vels)) < 0.01:
                vel_zero_times += 1
            else:
                vel_zero_times = 0
            if vel_zero_times > 10:
                success = False
                break
            rospy.sleep(self.configs.ARM.WAIT_MIN_TIME)
        return success

    def _setup_joint_pub(self):
        self.joint_pub = rospy.Publisher(
            self.configs.ARM.ROSTOPIC_SET_JOINT, JointState, queue_size=1
        )