diff --git a/convenience_tool_box/freedrive.py b/convenience_tool_box/freedrive.py deleted file mode 100644 index d31cea5394a47c3f510c3f3b5ea31d71f240372a..0000000000000000000000000000000000000000 --- a/convenience_tool_box/freedrive.py +++ /dev/null @@ -1,31 +0,0 @@ -import numpy as np -import time -from rtde_control import RTDEControlInterface -from rtde_receive import RTDEReceiveInterface -from rtde_io import RTDEIOInterface -import os -import copy -import signal - -def handler(signum, frame): - print('i will end freedrive and exit') - rtde_control.endFreedriveMode() - exit() - - -rtde_control = RTDEControlInterface("192.168.1.103") -rtde_receive = RTDEReceiveInterface("192.168.1.103") -rtde_io = RTDEIOInterface("192.168.1.103") -rtde_io.setSpeedSlider(0.2) -while not rtde_control.isConnected(): - continue -print("connected") - -rtde_control.freedriveMode() -signal.signal(signal.SIGINT, handler) - -while True: - q = rtde_receive.getActualQ() - q = np.array(q) - print(*q.round(3), sep=', ') - time.sleep(0.005) diff --git a/convenience_tool_box/freedrive_v2.0.py b/convenience_tool_box/freedrive_v2.0.py index 5c7c519fb3010e6e0f1782f1f1ecb9e8f6de7133..5577499de513dc50f4e6ea26078cda0200bf1ade 100644 --- a/convenience_tool_box/freedrive_v2.0.py +++ b/convenience_tool_box/freedrive_v2.0.py @@ -1,14 +1,5 @@ -import pinocchio as pin -import numpy as np -import time -import argparse -from functools import partial -from ur_simple_control.managers import ( - getMinimalArgParser, - ControlLoopManager, - RobotManager, -) -from ur_simple_control.basics.basics import freedriveUntilKeyboard +from smc import getMinimalArgParser, getRobotFromArgs +from smc.control.freedrive import freedriveUntilKeyboard import pickle @@ -24,7 +15,7 @@ def get_args(): if __name__ == "__main__": args = get_args() - robot = RobotManager(args) + robot = getRobotFromArgs(args) pose_n_q_dict = freedriveUntilKeyboard(args, robot) file = open("./pose_n_q_dict.pickle", "wb") @@ -33,15 +24,12 @@ if __name__ == "__main__": print(pose_n_q_dict) # get expected behaviour here (library can't know what the end is - you have to do this here) - if not args.pinocchio_only: + if args.real: robot.stopRobot() if args.save_log: - robot.log_manager.plotAllControlLoops() + robot._log_manager.saveLog() + robot._log_manager.plotAllControlLoops() if args.visualizer: robot.killManipulatorVisualizer() - - if args.save_log: - robot.log_manager.saveLog() - # loop_manager.stopHandler(None, None) diff --git a/examples/cartesian_space/clik_point_to_point.py b/examples/cartesian_space/clik_point_to_point.py index b1a8b8482befe3c34e1e258836247a7b141ee0b3..e0859528ee848e45b1b424eb8c0d7c0ee7ea8d9f 100644 --- a/examples/cartesian_space/clik_point_to_point.py +++ b/examples/cartesian_space/clik_point_to_point.py @@ -4,11 +4,10 @@ from smc.control.cartesian_space import getClikArgs from smc.robots.utils import defineGoalPointCLI from smc.control.cartesian_space.cartesian_space_point_to_point import moveL -import numpy as np import argparse -def get_args(): +def get_args() -> argparse.Namespace: parser = getMinimalArgParser() parser.description = "Run closed loop inverse kinematics \ of various kinds. Make sure you know what the goal is before you run!" @@ -17,7 +16,7 @@ def get_args(): "--randomly-generate-goal", action=argparse.BooleanOptionalAction, default=False, - help="if true, rand generate a goal, if false you type it in via text input", + help="if true, the target pose is randomly generated, if false you type it target translation in via text input", ) args = parser.parse_args() return args @@ -26,22 +25,20 @@ def get_args(): if __name__ == "__main__": args = get_args() robot = getRobotFromArgs(args) - if args.randomly_generate_goal: - Mgoal = getRandomlyGeneratedGoal(args) + if (not args.real) and args.randomly_generate_goal: + T_w_goal = getRandomlyGeneratedGoal(args) if args.visualizer: - robot.visualizer_manager.sendCommand({"Mgoal": Mgoal}) + robot.visualizer_manager.sendCommand({"Mgoal": T_w_goal}) else: - Mgoal = defineGoalPointCLI(robot) - Mgoal.rotation = np.eye(3) + if args.real and args.randomly_generate_goal: + print("Ain't no way you're going to a random goal on the real robot!") + print("Look at the current pose, define something appropriate manually") + T_w_goal = defineGoalPointCLI(robot) # compliantMoveL(args, robot, Mgoal) - if robot.robot_name != "yumi": - moveL(args, robot, Mgoal) - # else: - # goal_transform = pin.SE3.Identity() - # goal_transform.translation[1] = 0.1 - # moveLDualArm(args, robot, Mgoal, goal_transform) + moveL(args, robot, T_w_goal) robot.closeGripper() robot.openGripper() + if args.real: robot.stopRobot() @@ -50,4 +47,3 @@ if __name__ == "__main__": if args.save_log: robot._log_manager.saveLog() - # loop_manager.stopHandler(None, None) diff --git a/examples/cartesian_space/yumi_self_collision.py b/examples/cartesian_space/yumi_self_collision.py new file mode 100644 index 0000000000000000000000000000000000000000..f0c681ad0d121c605870dae1b0a188d513ea899a --- /dev/null +++ b/examples/cartesian_space/yumi_self_collision.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2022 Stéphane Caron + +"""Yumi two-armed manipulator with definition of custom frame and +collision avoidance w.r.t. those frames.""" + +import meshcat_shapes +import numpy as np +import pinocchio as pin +import qpsolvers +from loop_rate_limiters import RateLimiter + +import pink +from pink import solve_ik +from pink.barriers import BodySphericalBarrier +from pink.tasks import FrameTask, PostureTask +from pink.visualization import start_meshcat_visualizer + +try: + from robot_descriptions.loaders.pinocchio import load_robot_description +except ModuleNotFoundError as exc: + raise ModuleNotFoundError( + "Examples need robot_descriptions, " + "try `[conda|pip] install robot_descriptions`" + ) from exc # noqa: E501 + + +if __name__ == "__main__": + # Load the robot and define the custom frames + robot = load_robot_description("yumi_description", root_joint=None) + + # Left arm frame + l_frame_placement = pin.SE3() + l_frame_placement.translation = np.array([0.0, 0.0, 0.05]) + l_frame_placement.rotation = np.eye(3) + + l_frame = pin.Frame( + "yumi_barrier_l", + robot.model.getJointId("yumi_joint_6_l"), + robot.model.getFrameId("yumi_link_7_l"), + l_frame_placement, + pin.FrameType.OP_FRAME, + ) + + robot.model.addFrame(l_frame) + + # Right arm frame + r_frame_placement = pin.SE3() + r_frame_placement.translation = np.array([0.0, 0.0, 0.05]) + r_frame_placement.rotation = np.eye(3) + + r_frame = pin.Frame( + "yumi_barrier_r", + robot.model.getJointId("yumi_joint_6_r"), + robot.model.getFrameId("yumi_link_7_r"), + r_frame_placement, + pin.FrameType.OP_FRAME, + ) + + robot.model.addFrame(r_frame) + # Redefining robot data to take into account the new frames + robot.data = pin.Data(robot.model) + + viz = start_meshcat_visualizer(robot) + + # Pink tasks + left_end_effector_task = FrameTask( + "yumi_link_7_l", + position_cost=50.0, # [cost] / [m] + orientation_cost=10.0, # [cost] / [rad] + ) + right_end_effector_task = FrameTask( + "yumi_link_7_r", + position_cost=50.0, # [cost] / [m] + orientation_cost=10.0, # [cost] / [rad] + ) + + # Pink barriers + ee_barrier = BodySphericalBarrier( + ("yumi_barrier_l", "yumi_barrier_r"), + d_min=0.25, + gain=100.0, + safe_displacement_gain=1.0, + ) + + posture_task = PostureTask( + cost=1e-3, # [cost] / [rad] + ) + + cbf_list = [ee_barrier] + tasks = [left_end_effector_task, right_end_effector_task, posture_task] + + q_ref = np.array( + [ + 0.045, + -0.155, + -0.394, + -0.617, + -0.939, + -0.343, + -1.216, + 0, + 0, + -0.374, + -0.249, + 0.562, + -0.520, + 0.934, + -0.337, + 1.400, + 0, + 0, + ] + ) + configuration = pink.Configuration(robot.model, robot.data, q_ref) + for task in tasks: + task.set_target_from_configuration(configuration) + viz.display(configuration.q) + + viewer = viz.viewer + meshcat_shapes.frame(viewer["left_end_effector"], opacity=1.0) + meshcat_shapes.sphere( + viewer["left_ee_barrier"], + opacity=0.4, + color=0xFF0000, + radius=0.125, + ) + meshcat_shapes.sphere( + viewer["right_ee_barrier"], + opacity=0.4, + color=0x00FF00, + radius=0.125, + ) + meshcat_shapes.frame(viewer["right_end_effector"], opacity=1.0) + meshcat_shapes.frame(viewer["left_end_effector_target"], opacity=1.0) + meshcat_shapes.frame(viewer["right_end_effector_target"], opacity=1.0) + + # Select QP solver + solver = qpsolvers.available_solvers[0] + if "osqp" in qpsolvers.available_solvers: + solver = "osqp" + + rate = RateLimiter(frequency=200.0) + dt = rate.period + t = 0.0 # [s] + l_y_des = np.array([0.392, 0.392, 0.6]) + r_y_des = np.array([0.392, 0.392, 0.6]) + l_dy_des = np.zeros(3) + r_dy_des = np.zeros(3) + + while True: + # Calculate desired trajectory + A = 0.1 + B = 0.1 + # z -- 0.4 - 0.8 + l_y_des[:] = ( + 0.6, + 0.1 + B * np.sin(t), + 0.6 + A * np.sin(t), + ) + r_y_des[:] = ( + 0.6, + -0.1 - B * np.sin(t), + 0.6 + A * np.sin(t), + ) + l_dy_des[:] = 0, B * np.cos(t), A * np.cos(t) + r_dy_des[:] = 0, -B * np.cos(t), A * np.cos(t) + + left_end_effector_task.transform_target_to_world.translation = l_y_des + right_end_effector_task.transform_target_to_world.translation = r_y_des + + # Update visualization frames + viewer["left_end_effector"].set_transform( + configuration.get_transform_frame_to_world(left_end_effector_task.frame).np + ) + viewer["right_end_effector"].set_transform( + configuration.get_transform_frame_to_world(right_end_effector_task.frame).np + ) + viewer["left_end_effector_target"].set_transform( + left_end_effector_task.transform_target_to_world.np + ) + viewer["right_end_effector_target"].set_transform( + right_end_effector_task.transform_target_to_world.np + ) + + lb = configuration.get_transform_frame_to_world("yumi_barrier_l") + rb = configuration.get_transform_frame_to_world("yumi_barrier_r") + + viewer["left_ee_barrier"].set_transform(lb.np) + viewer["right_ee_barrier"].set_transform(rb.np) + + velocity = solve_ik( + configuration, + tasks, + dt, + solver=solver, + barriers=cbf_list, + ) + configuration.integrate_inplace(velocity, dt) + dist_ee = ( + np.linalg.norm( + configuration.get_transform_frame_to_world("yumi_barrier_l").translation + - configuration.get_transform_frame_to_world( + "yumi_barrier_r" + ).translation + ) + * 100 + ) + print(f"Distance between end effectors: {dist_ee :0.1f}cm") + # Visualize result at fixed FPS + viz.display(configuration.q) + rate.sleep() + t += dt diff --git a/examples/teaching_by_demonstration/naive_version.py b/examples/teaching_by_demonstration/naive_version.py new file mode 100644 index 0000000000000000000000000000000000000000..77fae4418a2cd391a946ad7938f6dfc0c44888e9 --- /dev/null +++ b/examples/teaching_by_demonstration/naive_version.py @@ -0,0 +1,46 @@ +from smc import getMinimalArgParser, getRobotFromArgs +from smc.control.freedrive import freedriveUntilKeyboard +from smc.control.cartesian_space.cartesian_space_point_to_point import moveL +from smc.control.cartesian_space import getClikArgs +import pickle + + +def get_args(): + parser = getMinimalArgParser() + parser = getClikArgs(parser) + parser.description = ( + "freedrive with keyboard bindings to save poses and joint angles" + ) + # add more arguments here from different Simple Manipulator Control modules + args = parser.parse_args() + return args + + +if __name__ == "__main__": + args = get_args() + robot = getRobotFromArgs(args) + + try: + file = open("./pose_n_q_dict.pickle", "rb") + pose_n_q_dict = pickle.load(file) + file.close() + except FileNotFoundError: + pose_n_q_dict = freedriveUntilKeyboard(args, robot) + file = open("./pose_n_q_dict.pickle", "wb") + pickle.dump(pose_n_q_dict, file) + file.close() + print(pose_n_q_dict) + + for T_w_goal in pose_n_q_dict["T_w_es"]: + moveL(args, robot, T_w_goal) + + # get expected behaviour here (library can't know what the end is - you have to do this here) + if args.real: + robot.stopRobot() + + if args.save_log: + robot._log_manager.saveLog() + robot._log_manager.plotAllControlLoops() + + if args.visualizer: + robot.killManipulatorVisualizer() diff --git a/python/smc/control/freedrive.py b/python/smc/control/freedrive.py index 25c8194d5840945a1a10bf931034e4691110d455..4ebb0558d2d03b1c24bb4422556002c1092dc26d 100644 --- a/python/smc/control/freedrive.py +++ b/python/smc/control/freedrive.py @@ -7,6 +7,7 @@ import threading from queue import Queue from argparse import Namespace from collections import deque +from pinocchio import SE3 def freedriveControlLoop( @@ -31,7 +32,7 @@ def freedriveControlLoop( save_past_dict = {} q = robot.q - T_w_e = robot.T_w_e() + T_w_e = robot.T_w_e if not com_queue.empty(): msg = com_queue.get() @@ -51,7 +52,9 @@ def freedriveControlLoop( return breakFlag, save_past_dict, log_item -def freedriveUntilKeyboard(args, robot: SingleArmInterface): +def freedriveUntilKeyboard( + args: Namespace, robot: SingleArmInterface +) -> dict[str, list[SE3] | np.ndarray]: """ controlLoopFreedrive ----------------------------- diff --git a/python/smc/robots/abstract_robotmanager.py b/python/smc/robots/abstract_robotmanager.py index dea74f558e82f7d3ae62cfa1d095dba5f38eab92..6605d92b26b30d35d11275bd55a6a3c8df16cb40 100644 --- a/python/smc/robots/abstract_robotmanager.py +++ b/python/smc/robots/abstract_robotmanager.py @@ -66,7 +66,7 @@ class AbstractRobotManager(abc.ABC): # and set this there. self.gripper = None # initialize things that depend on q here - self._step() + # self._step() #################################################################### # processes and utilities robotmanager owns # diff --git a/python/smc/robots/implementations/ur5e.py b/python/smc/robots/implementations/ur5e.py index ef7b7b832e2310a153bc289e7ac9003ec7e73ccc..6552d540ca9f2b7a08a291ea04185e095aacbf95 100644 --- a/python/smc/robots/implementations/ur5e.py +++ b/python/smc/robots/implementations/ur5e.py @@ -68,6 +68,13 @@ class RealUR5eRobotManager(AbstractUR5eRobotManager, AbstractRealRobotManager): self._rtde_control: RTDEControlInterface self._rtde_receive: RTDEReceiveInterface self._rtde_io: RTDEIOInterface + # TODO: copy-pasted from ForceTorqueSensorInterface's __init__ + # this should be inited automatically, it's not, todo is to make it work + self._wrench_base: np.ndarray = np.zeros(6) + self._wrench: np.ndarray = np.zeros(6) + # NOTE: wrench bias will be defined in the frame your sensor's gives readings + self._wrench_bias: np.ndarray = np.zeros(6) + self._T_w_e = pin.SE3.Identity() super().__init__(args) def connectToGripper(self): @@ -113,11 +120,12 @@ class RealUR5eRobotManager(AbstractUR5eRobotManager, AbstractRealRobotManager): self._rtde_control = RTDEControlInterface(self.args.robot_ip) self._rtde_receive = RTDEReceiveInterface(self.args.robot_ip) self._rtde_io = RTDEIOInterface(self.args.robot_ip) - self._rtde_io.setSpeedSlider(self.args.speed_slider) + self._rtde_io.setSpeedSlider(self._speed_slider) # NOTE: the force/torque sensor just has large offsets for no reason, # and you need to minus them to have usable readings. # we provide this with calibrateFT - self.wrench_offset = self.calibrateFT(self._dt) + #self.wrench_offset = self.calibrateFT(self._dt) + self.calibrateFT(self._dt) def setSpeedSlider(self, value): """ diff --git a/python/smc/robots/interfaces/force_torque_sensor_interface.py b/python/smc/robots/interfaces/force_torque_sensor_interface.py index 1264e077dae4a8c5f87b5aa7744345919a8c2299..90c50655eae93ac58fec0fe9d144ef75a71249d3 100644 --- a/python/smc/robots/interfaces/force_torque_sensor_interface.py +++ b/python/smc/robots/interfaces/force_torque_sensor_interface.py @@ -8,7 +8,9 @@ import time class ForceTorqueSensorInterface(abc.ABC): def __init__(self): # in base frame - print("ForceTorqueSensorInterface init") + # TODO: update so that __init__ accepts args + # if args.debug_prints: + # print("ForceTorqueSensorInterface init") self._wrench_base: np.ndarray = np.zeros(6) self._wrench: np.ndarray = np.zeros(6) # NOTE: wrench bias will be defined in the frame your sensor's gives readings @@ -87,8 +89,9 @@ class ForceTorqueSensorInterface(abc.ABC): self.zeroFtSensor() for _ in range(2000): start = time.time() - ft = self._updateWrench() - ft_readings.append(ft) + # ft = self._updateWrench() + self._updateWrench() + ft_readings.append(self._wrench_base.copy()) end = time.time() diff = end - start if diff < dt: @@ -97,10 +100,15 @@ class ForceTorqueSensorInterface(abc.ABC): ft_readings = np.array(ft_readings) self._wrench_bias = np.average(ft_readings, axis=0) print("The wrench bias is:", self._wrench_bias) - return self._wrench_bias.copy() class ForceTorqueOnSingleArmWrist(SingleArmInterface, ForceTorqueSensorInterface): + def __init__(self, args): + if args.debug_prints: + print("ForceTorqueOnSingleArmWrist init") + ForceTorqueSensorInterface.__init__(args) + super().__init__(args) + @property def ft_sensor_frame_id(self) -> int: return self._ee_frame_id diff --git a/python/smc/robots/utils.py b/python/smc/robots/utils.py index c53dda8f8f6047346006d905add20871b74320a8..faf7c5c4942d4af97cb1c2a1b4b4acc84e21e13d 100644 --- a/python/smc/robots/utils.py +++ b/python/smc/robots/utils.py @@ -196,9 +196,9 @@ def defineGoalPointCLI(robot): in the case you're visualizing, makes no sense otherwise. """ robot._step() - q = robot.q + robot._step() # define goal - T_w_e = robot.T_w_e + T_w_goal = robot.T_w_e print("You can only specify the translation right now.") if robot.args.real: print( @@ -212,13 +212,12 @@ def defineGoalPointCLI(robot): *robot.data.oMi[6].translation.round(4), *pin.rpy.matrixToRpy(robot.data.oMi[6].rotation).round(4), ) - print("UR5e TCP:", *np.array(robot.rtde_receive.getActualTCPPose()).round(4)) + print("UR5e TCP:", *np.array(robot._rtde_receive.getActualTCPPose()).round(4)) # remain with the current orientation # TODO: add something, probably rpy for orientation because it's the least number # of numbers you need to type in - Mgoal = T_w_e.copy() # this is a reasonable way to do it too, maybe implement it later - # Mgoal.translation = Mgoal.translation + np.array([0.0, 0.0, -0.1]) + # T_w_goal.translation = T_w_goal.translation + np.array([0.0, 0.0, -0.1]) # do a while loop until this is parsed correctly while True: goal = input( @@ -234,17 +233,13 @@ def defineGoalPointCLI(robot): print("The input is not in the expected format. Try again.") print(e) if e == "ok": - Mgoal.translation = np.array(goal_list) + T_w_goal.translation = np.array(goal_list) break - print("this is goal pose you defined:\n", Mgoal) + print("this is goal pose you defined:\n", T_w_goal) - # NOTE i'm not deepcopying this on purpose - # but that might be the preferred thing, we'll see - robot.Mgoal = Mgoal if robot.args.visualizer: - # TODO document this somewhere - robot.visualizer_manager.sendCommand({"Mgoal": Mgoal}) - return Mgoal + robot.visualizer_manager.sendCommand({"Mgoal": T_w_goal}) + return T_w_goal # TODO: finish diff --git a/python/smc/util/define_random_goal.py b/python/smc/util/define_random_goal.py index 40ee89eff5d10429d6aaaa5290232b1b5c552ff2..364e082204e0bde093716ac076c9e381ba080592 100644 --- a/python/smc/util/define_random_goal.py +++ b/python/smc/util/define_random_goal.py @@ -1,14 +1,14 @@ -# PYTHON_ARGCOMPLETE_OK import numpy as np -import pinocchio as pin +from pinocchio import SE3 + def getRandomlyGeneratedGoal(args): - Mgoal = pin.SE3.Random() + T_w_goal = SE3.Random() # has to be close - translation = np.random.random(3) * 0.4 + translation = np.random.random(3) * 0.8 - 0.4 translation[2] = np.abs(translation[2]) translation = translation + np.ones(3) * 0.1 - Mgoal.translation = translation + T_w_goal.translation = translation if args.debug_prints: - print(Mgoal) - return Mgoal + print(T_w_goal) + return T_w_goal