diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..05c523d --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python 调试程序: 当前文件", + "type": "debugpy", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal" + } + ] +} \ No newline at end of file diff --git a/Asset/xarm7_robot.urdf b/Asset/xarm7_robot.urdf new file mode 100644 index 0000000..6874b74 --- /dev/null +++ b/Asset/xarm7_robot.urdf @@ -0,0 +1,359 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + transmission_interface/SimpleTransmission + + hardware_interface/PositionJointInterface + + + hardware_interface/PositionJointInterface + 100 + + + + transmission_interface/SimpleTransmission + + hardware_interface/PositionJointInterface + + + hardware_interface/PositionJointInterface + 100 + + + + transmission_interface/SimpleTransmission + + hardware_interface/PositionJointInterface + + + hardware_interface/PositionJointInterface + 100 + + + + transmission_interface/SimpleTransmission + + hardware_interface/PositionJointInterface + + + hardware_interface/PositionJointInterface + 100 + + + + transmission_interface/SimpleTransmission + + hardware_interface/PositionJointInterface + + + hardware_interface/PositionJointInterface + 100 + + + + transmission_interface/SimpleTransmission + + hardware_interface/PositionJointInterface + + + hardware_interface/PositionJointInterface + 100 + + + + transmission_interface/SimpleTransmission + + hardware_interface/PositionJointInterface + + + hardware_interface/PositionJointInterface + 100 + + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + + + /xarm + + gazebo_ros_control/DefaultRobotHWSim + true + + + + + diff --git a/Examples/close_gripper.py b/Examples/close_gripper.py deleted file mode 100644 index 64d38df..0000000 --- a/Examples/close_gripper.py +++ /dev/null @@ -1,52 +0,0 @@ -''' -Run this script using: - -python3 close_gripper.py 192.168.1.208 -''' - -import sys -import os -parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) -sys.path.append(os.path.join(parent_dir, 'RoboticsToolBox')) -# import pyRobotiqGripper -from Bestman_real_xarm6 import * -from time import time, sleep -import numpy as np -def main(): - # Parse Arguments - argparser = argparse.ArgumentParser(description="Xarm6.") - # Required arguments - argparser.add_argument("robot_ip", help="IP address of the robot server") - # argparser.add_argument("local_ip", help="IP address of this PC") - # argparser.add_argument("frequency", type=int, help="Command frequency, 1 to 200 [Hz]") - # Optional arguments - # argparser.add_argument("--hold", action="store_true", help="Robot holds current joint positions, otherwise do a sine-sweep") - args = argparser.parse_args() - - try: - # Instantiate the robot interface - bestman = Bestman_Real_Xarm6(args.robot_ip, None, None) - - # Clear fault on the robot server if any - # if bestman.robot.isFault(): - # log.warn("Fault occurred on the robot server, trying to clear ...") - # bestman.robot.clearFault() - # time.sleep(2) - # if bestman.robot.isFault(): - # log.error("Fault cannot be cleared, exiting ...") - # return - # log.info("Fault on the robot server is cleared") - - # Close gripper - bestman.find_gripper() - bestman.close_gripper() - print(bestman.get_current_end_effector_pose()) - - - except Exception as e: - # Log any exceptions that occur - print(str(e)) - - -if __name__ == "__main__": - main() diff --git a/Examples/close_gripper_robotiq.py b/Examples/close_gripper_robotiq.py new file mode 100644 index 0000000..5c8f9c3 --- /dev/null +++ b/Examples/close_gripper_robotiq.py @@ -0,0 +1,42 @@ +# !/usr/bin/env python +# -*- encoding: utf-8 -*- +""" +# @FileName : close_gripper_robotiq.py +# @Time : 2024-12-01 15:21:45 +# @Author : Zhaxi & Yan +# @Email : zhaxizhuoma.ayang@gmail.com & yding25@binghamton.edu +# @Description : Close robotiq gripper +# @Usage : python close_gripper_robotiq.py 192.168.1.208 +""" + +import argparse +import time +from Robotics_API import Bestman_Real_Xarm6 + +def main(): + # Parse Arguments + argparser = argparse.ArgumentParser( + description="Move the robot arm to follow a trajectory." + ) + # Required arguments + argparser.add_argument("robot_ip", help="IP address of the robot server") + args = argparser.parse_args() + + try: + # Instantiate the robot interface + bestman = Bestman_Real_Xarm6(args.robot_ip) + + # Initialize robot + if not bestman.initialize_robot(): + return # Exit if initialization fails + + bestman.close_gripper_robotiq() + time.sleep(1) + + except Exception as e: + # Log any exceptions that occur + print(str(e)) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/Examples/display_states.py b/Examples/display_states.py index 02f6b27..e27d532 100644 --- a/Examples/display_states.py +++ b/Examples/display_states.py @@ -1,72 +1,41 @@ -''' -Run this script using: -python /home/$(whoami)/BestMan_Flexiv/Examples/display_states.py 192.168.2.100 192.168.2.108 -''' - -import sys -import os -parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) -sys.path.append(os.path.join(parent_dir, 'RoboticsToolBox')) -from Bestman_sim_flexiv import * - -def print_robot_states(bestman): - """ - Print robot states data @ 1Hz. - """ - while True: - # Get the latest robot states - bestman.update_robot_states() - robot_states = bestman.robot_states - - # Print all robot states, round all float values to 2 decimals - print("{") - print("q: ", ['%.2f' % i for i in robot_states.q]) - print("theta: ", ['%.2f' % i for i in robot_states.theta]) - print("dq: ", ['%.2f' % i for i in robot_states.dq]) - print("dtheta: ", ['%.2f' % i for i in robot_states.dtheta]) - print("tau: ", ['%.2f' % i for i in robot_states.tau]) - print("tau_des: ", ['%.2f' % i for i in robot_states.tauDes]) - print("tau_dot: ", ['%.2f' % i for i in robot_states.tauDot]) - print("tau_ext: ", ['%.2f' % i for i in robot_states.tauExt]) - print("tcp_pose(quaternion): ", ['%.2f' % i for i in robot_states.tcpPose]) - print("tcp_pose_d: ", ['%.2f' % i for i in robot_states.tcpPoseDes]) - print("tcp_velocity: ", ['%.2f' % i for i in robot_states.tcpVel]) - print("camera_pose: ", ['%.2f' % i for i in robot_states.camPose]) - print("flange_pose: ", ['%.2f' % i for i in robot_states.flangePose]) - print("FT_sensor_raw_reading: ", ['%.2f' % i for i in robot_states.ftSensorRaw]) - print("F_ext_tcp_frame: ", ['%.2f' % i for i in robot_states.extWrenchInTcp]) - print("F_ext_base_frame: ", ['%.2f' % i for i in robot_states.extWrenchInBase]) - print("}" + '\n') - time.sleep(1) +# !/usr/bin/env python +# -*- encoding: utf-8 -*- +""" +# @FileName : display_states.py +# @Time : 2024-12-01 15:21:45 +# @Author : Zhaxi & Yan +# @Email : zhaxizhuoma.ayang@gmail.com & yding25@binghamton.edu +# @Description : Display arm state +# @Usage : python display_states.py 192.168.1.208 +""" + +import argparse +import time +from Robotics_API import Bestman_Real_Xarm7, Pose def main(): - # Parse arguments - argparser = argparse.ArgumentParser() + # Parse Arguments + argparser = argparse.ArgumentParser( + description="Move the robot arm to follow a trajectory." + ) + # Required arguments argparser.add_argument("robot_ip", help="IP address of the robot server") - argparser.add_argument("local_ip", help="IP address of this PC") args = argparser.parse_args() - # Instantiate Bestman_Real_Flexiv - bestman = Bestman_Real_Flexiv(args.robot_ip, args.local_ip, frequency=1) - - # Print description - print( - "This tutorial does the very first thing: check connection with the robot server " - "and print received robot states." - ) - try: - # Initialize and clear faults - bestman.clear_fault() + # Instantiate the robot interface + bestman = Bestman_Real_Xarm7(args.robot_ip) - # Print States - print_thread = threading.Thread(target=print_robot_states, args=[bestman]) - print_thread.start() - print_thread.join() + # Initialize robot + if not bestman.initialize_robot(): + return # Exit if initialization fails + + bestman.update_robot_states() except Exception as e: - # Print exception error message - bestman.log.error(str(e)) + # Log any exceptions that occur + print(str(e)) + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/Examples/move_arm_follow_joint_angles.py b/Examples/move_arm_follow_joint_angles.py deleted file mode 100644 index a791581..0000000 --- a/Examples/move_arm_follow_joint_angles.py +++ /dev/null @@ -1,90 +0,0 @@ -''' -Run this script using: - -python move_arm_follow_joint_angles.py 192.168.2.100 192.168.2.108 20 -''' - -import sys -import os -parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) -sys.path.append(os.path.join(parent_dir, 'RoboticsToolBox')) -import pyRobotiqGripper -from Bestman_sim_flexiv import * - -def main(): - # Parse Arguments - argparser = argparse.ArgumentParser(description="Move the robot arm to follow a trajectory.") - # Required arguments - argparser.add_argument("robot_ip", help="IP address of the robot server") - argparser.add_argument("local_ip", help="IP address of this PC") - argparser.add_argument("frequency", type=int, help="Command frequency, 1 to 200 [Hz]") - # Optional arguments - argparser.add_argument("--hold", action="store_true", help="Robot holds current joint positions, otherwise do a sine-sweep") - args = argparser.parse_args() - - # Validate the frequency argument - frequency = args.frequency - assert 1 <= frequency <= 200, "Invalid input" - - log = flexivrdk.Log() - - try: - # Instantiate the robot interface - bestman = Bestman_Real_Flexiv(args.robot_ip, args.local_ip, args.frequency) - - # Clear fault on the robot server if any - if bestman.robot.isFault(): - log.warn("Fault occurred on the robot server, trying to clear ...") - bestman.robot.clearFault() - time.sleep(2) - if bestman.robot.isFault(): - log.error("Fault cannot be cleared, exiting ...") - return - log.info("Fault on the robot server is cleared") - - # Enable the robot, ensuring the E-stop is released before enabling - log.info("Enabling robot ...") - bestman.robot.enable() - - # Wait for the robot to become operational - seconds_waited = 0 - while not bestman.robot.isOperational(): - time.sleep(1) - seconds_waited += 1 - if seconds_waited == 10: - log.warn( - "Still waiting for robot to become operational. Please " - "check that the robot 1) has no fault, 2) is booted " - "into Auto mode" - ) - - log.info("Robot is now operational") - - # Get and log current joint values and bounds - joint_angles = bestman.get_current_joint_angles() - log.info(f"Current joint values: {joint_angles}") - - joint_bounds = bestman.get_joint_bounds() - log.info(f"Current joint bounds: {joint_bounds}") - - # Define the target trajectory - target_trajectory = [ - [0.4, -0.5, 0, 1.3, 0, 0.5, 0.2], - [0.5, -0.55, 0.1, 1.3, 0, 0.5, 0.2], - [0.55, -0.6, 0.2, 1.3, 0, 0.5, 0.2], - [0.6, -0.7, 0, 1.3, 0, 0.5, 0.2], - [0.7, -0.7, 0, 1.3, 0, 0.5, 0.2], - [0.8, -0.7, 0, 1.3, 0, 0.5, 0.2] - ] - - # Move the arm to follow the target trajectory - bestman.move_arm_follow_joint_angles(target_trajectory) - time.sleep(1) - - except Exception as e: - # Log any exceptions that occur - log.error(str(e)) - - -if __name__ == "__main__": - main() diff --git a/Examples/move_arm_to_follow_trajectory.py b/Examples/move_arm_to_follow_trajectory.py deleted file mode 100644 index 9ae9b95..0000000 --- a/Examples/move_arm_to_follow_trajectory.py +++ /dev/null @@ -1,108 +0,0 @@ -''' -Run this script using: - -python move_arm_to_follow_trajectory.py 192.168.2.100 192.168.2.108 20 -''' - -import sys -import os -parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) -sys.path.append(os.path.join(parent_dir, 'RoboticsToolBox')) -# import pyRobotiqGripper -from Bestman_sim_flexiv import * - -def main(): - # Parse Arguments - argparser = argparse.ArgumentParser(description="Move the robot arm to follow a trajectory.") - # Required arguments - argparser.add_argument("robot_ip", help="IP address of the robot server") - argparser.add_argument("local_ip", help="IP address of this PC") - argparser.add_argument("frequency", type=int, help="Command frequency, 1 to 200 [Hz]") - # Optional arguments - argparser.add_argument("--hold", action="store_true", help="Robot holds current joint positions, otherwise do a sine-sweep") - args = argparser.parse_args() - - # Validate the frequency argument - frequency = args.frequency - assert 1 <= frequency <= 200, "Invalid input" - - log = flexivrdk.Log() - - try: - # Instantiate the robot interface - bestman = Bestman_Real_Flexiv(args.robot_ip, args.local_ip, args.frequency) - - # Clear fault on the robot server if any - if bestman.robot.isFault(): - log.warn("Fault occurred on the robot server, trying to clear ...") - bestman.robot.clearFault() - time.sleep(2) - if bestman.robot.isFault(): - log.error("Fault cannot be cleared, exiting ...") - return - log.info("Fault on the robot server is cleared") - - # Enable the robot, ensuring the E-stop is released before enabling - log.info("Enabling robot ...") - bestman.robot.enable() - - # Wait for the robot to become operational - seconds_waited = 0 - while not bestman.robot.isOperational(): - time.sleep(1) - seconds_waited += 1 - if seconds_waited == 10: - log.warn( - "Still waiting for robot to become operational. Please " - "check that the robot 1) has no fault, 2) is booted " - "into Auto mode" - ) - - log.info("Robot is now operational") - - # Get and log current joint values and bounds - joint_angles = bestman.get_current_joint_angles() - log.info(f"Current joint angles: {joint_angles}") - - joint_bounds = bestman.get_joint_bounds() - log.info(f"Current joint bounds: {joint_bounds}") - - target_trajectory = [ - [0.5729730725288391, -0.05068958178162575, 0.19427451491355896, 0.2969912886619568, -0.6381924748420715, 0.6559267044067383, -0.27251917123794556], - [0.5860132575035095, -0.03786855563521385, 0.23408854007720947, 0.33398041129112244, -0.6333060264587402, 0.6339964866638184, -0.29228243231773376], - [0.5932722687721252, -0.00427872221916914, 0.2615146338939667, -0.36070218682289124, 0.6458043456077576, -0.6061388254165649, 0.2922779619693756], - [0.5901758074760437, 0.06993594765663147, 0.2914247214794159, -0.3982785642147064, 0.6554641127586365, -0.5877817869186401, 0.25739768147468567], - [0.5728914141654968, 0.16257865726947784, 0.3004828095436096, -0.4288429319858551, 0.6845492124557495, -0.5475476384162903, 0.21835222840309143], - [0.5349975824356079, 0.2612200081348419, 0.29423990845680237, -0.4434780478477478, 0.7227771282196045, -0.5031689405441284, 0.1665576845407486], - [0.502289354801178, 0.32907742261886597, 0.24723584949970245, -0.3830461800098419, 0.7727231979370117, -0.4946635961532593, 0.1071559339761734], - [0.49048033356666565, 0.33556684851646423, 0.1762292981147766, -0.33498552441596985, 0.7840493321418762, -0.5154750943183899, 0.0856550931930542], - [0.49339544773101807, 0.334873765707016, 0.12699848413467407, -0.3229883313179016, 0.7895045876502991, -0.5138758420944214, 0.09106409549713135], - [0.4951143264770508, 0.2605552673339844, 0.10749507695436478, -0.29603084921836853, 0.7468422651290894, -0.5826343894004822, 0.1230020746588707], - [0.5105777978897095, 0.17725078761577606, 0.0976296067237854, -0.291313499212265, 0.7007455229759216, -0.6215708255767822, 0.19427280128002167], - [0.5435065627098083, 0.06313088536262512, 0.11161068826913834, -0.297743558883667, 0.6461387276649475, -0.6455997824668884, 0.2775867283344269], - [0.5594496726989746, -0.03314505144953728, 0.1478213518857956, 0.3072815537452698, -0.579055905342102, 0.6743392944335938, -0.33990997076034546], - [0.5756168365478516, -0.06275233626365662, 0.2009924054145813, 0.34431394934654236, -0.5344999432563782, 0.6804036498069763, -0.36442917585372925], - [0.5784454345703125, -0.12293380498886108, 0.2624003291130066, 0.3557124137878418, -0.48897919058799744, 0.6842485666275024, -0.4076419472694397], - [0.5636951923370361, -0.21520644426345825, 0.3116268813610077, 0.3457571268081665, -0.44096311926841736, 0.6897113919258118, -0.45858660340309143], - [0.5404208302497864, -0.31854310631752014, 0.24892762303352356, 0.2763579487800598, -0.5303931832313538, 0.6992200016975403, -0.391664057970047], - [0.5138706564903259, -0.29581376910209656, 0.1351354867219925, 0.24635033309459686, -0.6483463048934937, 0.6676037907600403, -0.2706727385520935], - [0.5513743758201599, -0.2239120602607727, 0.08924050629138947, -0.3200412094593048, 0.6891423463821411, -0.6101202964782715, 0.2245209813117981], - [0.5742518901824951, -0.1305057555437088, 0.09171643108129501, -0.3716944754123688, 0.69478440284729, -0.5891148447990417, 0.17905732989311218], - [0.5915540456771851, -0.039267927408218384, 0.10550501197576523, -0.4135972261428833, 0.6998876333236694, -0.5652957558631897, 0.1397687941789627], - [0.6060796976089478, 0.025441067293286324, 0.11670827120542526, -0.42734822630882263, 0.7028806209564209, -0.5518678426742554, 0.1370190531015396], - [0.6179891228675842, 0.0633757933974266, 0.13044734299182892, -0.41930848360061646, 0.6576049327850342, -0.593339741230011, 0.19920873641967773], - [0.6067739725112915, 0.06295245885848999, 0.18496927618980408, 0.40669456124305725, -0.5851887464523315, 0.6297315955162048, -0.3091791570186615], - [0.6095121502876282, 0.056209687143564224, 0.23338516056537628, 0.41905108094215393, -0.5604040026664734, 0.6283918023109436, -0.33980485796928406], - [0.6121770143508911, 0.04117409512400627, 0.2725994288921356, 0.4189033806324005, -0.5498623847961426, 0.6234208345413208, -0.36540088057518005] - ] - - bestman.move_end_effector_follow_trajectory(target_trajectory, max_linear_vel=0.1, max_angular_vel=0.5) - time.sleep(1) - - except Exception as e: - # Log any exceptions that occur - log.error(str(e)) - - -if __name__ == "__main__": - main() diff --git a/Examples/move_arm_to_home.py b/Examples/move_arm_to_home.py index 715ca7a..c4491da 100644 --- a/Examples/move_arm_to_home.py +++ b/Examples/move_arm_to_home.py @@ -1,49 +1,38 @@ -''' -Run this script using: - -python3 move_arm_to_home.py 192.168.1.208 -''' - -import sys -import os -parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) -sys.path.append(os.path.join(parent_dir, 'RoboticsToolBox')) -# import pyRobotiqGripper -from Bestman_real_xarm6 import * -from time import time, sleep +# !/usr/bin/env python +# -*- encoding: utf-8 -*- +""" +# @FileName : move_arm_to_home.py +# @Time : 2024-12-01 15:21:45 +# @Author : Zhaxi & Yan +# @Email : zhaxizhuoma.ayang@gmail.com & yding25@binghamton.edu +# @Description : Move arm to default home position +# @Usage : python move_arm_to_home.py 192.168.1.208 +""" + +import argparse +import time +from Robotics_API import Bestman_Real_Xarm7 def main(): # Parse Arguments - argparser = argparse.ArgumentParser(description="Xarm6.") + argparser = argparse.ArgumentParser( + description="Move the robot arm to follow a trajectory." + ) # Required arguments argparser.add_argument("robot_ip", help="IP address of the robot server") - # argparser.add_argument("local_ip", help="IP address of this PC") - # argparser.add_argument("frequency", type=int, help="Command frequency, 1 to 200 [Hz]") - # Optional arguments - # argparser.add_argument("--hold", action="store_true", help="Robot holds current joint positions, otherwise do a sine-sweep") args = argparser.parse_args() try: # Instantiate the robot interface - bestman = Bestman_Real_Xarm6(args.robot_ip, None, None) - - # Clear fault on the robot server if any - # if bestman.robot.isFault(): - # log.warn("Fault occurred on the robot server, trying to clear ...") - # bestman.robot.clearFault() - # time.sleep(2) - # if bestman.robot.isFault(): - # log.error("Fault cannot be cleared, exiting ...") - # return - # log.info("Fault on the robot server is cleared") - - # Enable the robot, ensuring the E-stop is released before enabling - # log.info("Enabling robot ...") - # bestman.robot.enable() + bestman = Bestman_Real_Xarm7(args.robot_ip) + + # Initialize robot + if not bestman.initialize_robot(): + return # Exit if initialization fails # Go back to home pose - bestman.go_home() - sleep(1) + bestman.reset_to_home() + time.sleep(1) except Exception as e: # Log any exceptions that occur diff --git a/Examples/move_arm_to_joint_angles.py b/Examples/move_arm_to_joint_angles.py deleted file mode 100644 index eac8fe5..0000000 --- a/Examples/move_arm_to_joint_angles.py +++ /dev/null @@ -1,63 +0,0 @@ -''' -Run this script using: - -python3 move_arm_to_joint_angles.py 192.168.1.208 -''' - -import sys -import os -parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) -sys.path.append(os.path.join(parent_dir, 'RoboticsToolBox')) -# import pyRobotiqGripper -from Bestman_real_xarm6 import * -from time import time, sleep -import numpy as np -def main(): - # Parse Arguments - argparser = argparse.ArgumentParser(description="Xarm6.") - # Required arguments - argparser.add_argument("robot_ip", help="IP address of the robot server") - # argparser.add_argument("local_ip", help="IP address of this PC") - # argparser.add_argument("frequency", type=int, help="Command frequency, 1 to 200 [Hz]") - # Optional arguments - # argparser.add_argument("--hold", action="store_true", help="Robot holds current joint positions, otherwise do a sine-sweep") - args = argparser.parse_args() - - try: - # Instantiate the robot interface - bestman = Bestman_Real_Xarm6(args.robot_ip, None, None) - - # Clear fault on the robot server if any - # if bestman.robot.isFault(): - # log.warn("Fault occurred on the robot server, trying to clear ...") - # bestman.robot.clearFault() - # time.sleep(2) - # if bestman.robot.isFault(): - # log.error("Fault cannot be cleared, exiting ...") - # return - # log.info("Fault on the robot server is cleared") - - # Enable the robot, ensuring the E-stop is released before enabling - # log.info("Enabling robot ...") - # bestman.robot.enable() - bestman.go_home() - - _joint_angles = [0.1, 0.1, 0.1, 0.1, 0.1, 0.1] - - # Go back to home pose - for i in range(10): - - _joint_angles = np.add(_joint_angles, [-0.05, -0.05, -0.05, -0.05, -0.05, -0.05]) - print(_joint_angles) - bestman.move_arm_to_joint_angles(joint_angles=_joint_angles, wait_for_finish=False) - sleep(1) - bestman.go_home() - - - except Exception as e: - # Log any exceptions that occur - print(str(e)) - - -if __name__ == "__main__": - main() diff --git a/Examples/move_arm_to_joint_values.py b/Examples/move_arm_to_joint_values.py new file mode 100644 index 0000000..4f142e8 --- /dev/null +++ b/Examples/move_arm_to_joint_values.py @@ -0,0 +1,44 @@ +# !/usr/bin/env python +# -*- encoding: utf-8 -*- +""" +# @FileName : move_arm_to_joint_values.py +# @Time : 2024-12-01 15:21:45 +# @Author : Zhaxi & Yan +# @Email : zhaxizhuoma.ayang@gmail.com & yding25@binghamton.edu +# @Description : Move arm to Move arm to targeted joint values +# @Usage : python move_arm_to_joint_values.py 192.168.1.208 +""" + +import argparse +import time +from Robotics_API import Bestman_Real_Xarm6 + +def main(): + # Parse Arguments + argparser = argparse.ArgumentParser( + description="Move the robot arm to follow a trajectory." + ) + # Required arguments + argparser.add_argument("robot_ip", help="IP address of the robot server") + args = argparser.parse_args() + + try: + # Instantiate the robot interface + bestman = Bestman_Real_Xarm6(args.robot_ip) + + # Initialize robot + if not bestman.initialize_robot(): + return # Exit if initialization fails + + import math + target_joint = [16.8, -24.1, -24.7, 11.2, -77.7, -5.1] + target_joint = [math.radians(target_joint[n]) for n in range(6)] + bestman.set_joint_cmd(target_joint) + + except Exception as e: + # Log any exceptions that occur + print(str(e)) + + +if __name__ == "__main__": + main() diff --git a/Examples/move_eef_to_pose.py b/Examples/move_eef_to_pose.py new file mode 100644 index 0000000..8cd58d4 --- /dev/null +++ b/Examples/move_eef_to_pose.py @@ -0,0 +1,48 @@ +# !/usr/bin/env python +# -*- encoding: utf-8 -*- +""" +# @FileName : move_eef_to_pose.py +# @Time : 2024-12-01 15:21:45 +# @Author : Zhaxi & Yan +# @Email : zhaxizhuoma.ayang@gmail.com & yding25@binghamton.edu +# @Description : Move end effector to targeted pose +# @Usage : python move_eef_to_pose.py 192.168.1.208 +""" + +import argparse +import time +from Robotics_API import Bestman_Real_Xarm7, Pose +import numpy as np + +def main(): + # Parse Arguments + argparser = argparse.ArgumentParser( + description="Move the robot arm to follow a trajectory." + ) + # Required arguments + argparser.add_argument("robot_ip", help="IP address of the robot server") + args = argparser.parse_args() + + try: + # Instantiate the robot interface + bestman = Bestman_Real_Xarm7(args.robot_ip) + + # Initialize robot + if not bestman.initialize_robot(): + return # Exit if initialization fails + + # Define the target trajectory + angles_rad = np.radians([-84.09874516930229, -83.31184493346971, -98.6411015590766]) + mvpose = Pose([0.5414959720000001, -0.0031613120000000003, 0.302334076], angles_rad) + # target_pose = Pose([0.420434235, -0.00581615, 0.459876038], [0.7071068218077394, -0.00032550013355177364, 0.7071065907288765, 0.0003255002399235735]) # 四元数 + + # Move the arm to follow the target trajectory + bestman.set_eef_cmd(mvpose) + + except Exception as e: + # Log any exceptions that occur + print(str(e)) + + +if __name__ == "__main__": + main() diff --git a/Examples/open_gripper.py b/Examples/open_gripper.py deleted file mode 100644 index 7310c20..0000000 --- a/Examples/open_gripper.py +++ /dev/null @@ -1,50 +0,0 @@ -''' -Run this script using: - -python3 open_gripper.py 192.168.1.208 -''' - -import sys -import os -parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) -sys.path.append(os.path.join(parent_dir, 'RoboticsToolBox')) -# import pyRobotiqGripper -from Bestman_real_xarm6 import * -from time import time, sleep -import numpy as np -def main(): - # Parse Arguments - argparser = argparse.ArgumentParser(description="Xarm6.") - # Required arguments - argparser.add_argument("robot_ip", help="IP address of the robot server") - # argparser.add_argument("local_ip", help="IP address of this PC") - # argparser.add_argument("frequency", type=int, help="Command frequency, 1 to 200 [Hz]") - # Optional arguments - # argparser.add_argument("--hold", action="store_true", help="Robot holds current joint positions, otherwise do a sine-sweep") - args = argparser.parse_args() - - try: - # Instantiate the robot interface - bestman = Bestman_Real_Xarm6(args.robot_ip, None, None) - - # Clear fault on the robot server if any - # if bestman.robot.isFault(): - # log.warn("Fault occurred on the robot server, trying to clear ...") - # bestman.robot.clearFault() - # time.sleep(2) - # if bestman.robot.isFault(): - # log.error("Fault cannot be cleared, exiting ...") - # return - # log.info("Fault on the robot server is cleared") - - # open gripper - bestman.open_gripper() - - - except Exception as e: - # Log any exceptions that occur - print(str(e)) - - -if __name__ == "__main__": - main() diff --git a/Examples/open_gripper_robotiq.py b/Examples/open_gripper_robotiq.py new file mode 100644 index 0000000..e4a4985 --- /dev/null +++ b/Examples/open_gripper_robotiq.py @@ -0,0 +1,42 @@ +# !/usr/bin/env python +# -*- encoding: utf-8 -*- +""" +# @FileName : open_gripper_robotiq.py +# @Time : 2024-12-01 15:21:45 +# @Author : Zhaxi & Yan +# @Email : zhaxizhuoma.ayang@gmail.com & yding25@binghamton.edu +# @Description : Open robotiq gripper +# @Usage : python open_gripper_robotiq.py 192.168.1.208 +""" + +import argparse +import time +from Robotics_API import Bestman_Real_Xarm6 + +def main(): + # Parse Arguments + argparser = argparse.ArgumentParser( + description="Move the robot arm to follow a trajectory." + ) + # Required arguments + argparser.add_argument("robot_ip", help="IP address of the robot server") + args = argparser.parse_args() + + try: + # Instantiate the robot interface + bestman = Bestman_Real_Xarm6(args.robot_ip) + + # Initialize robot + if not bestman.initialize_robot(): + return # Exit if initialization fails + + bestman.open_gripper_robotiq() + time.sleep(1) + + except Exception as e: + # Log any exceptions that occur + print(str(e)) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/Examples/open_gripper_width.py b/Examples/open_gripper_width.py deleted file mode 100644 index 31963a4..0000000 --- a/Examples/open_gripper_width.py +++ /dev/null @@ -1,50 +0,0 @@ -''' -Run this script using: - -python3 open_gripper_width.py 192.168.1.208 -''' - -import sys -import os -parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) -sys.path.append(os.path.join(parent_dir, 'RoboticsToolBox')) -# import pyRobotiqGripper -from Bestman_real_xarm6 import * -from time import time, sleep -import numpy as np -def main(): - # Parse Arguments - argparser = argparse.ArgumentParser(description="Xarm6.") - # Required arguments - argparser.add_argument("robot_ip", help="IP address of the robot server") - # argparser.add_argument("local_ip", help="IP address of this PC") - # argparser.add_argument("frequency", type=int, help="Command frequency, 1 to 200 [Hz]") - # Optional arguments - # argparser.add_argument("--hold", action="store_true", help="Robot holds current joint positions, otherwise do a sine-sweep") - args = argparser.parse_args() - - try: - # Instantiate the robot interface - bestman = Bestman_Real_Xarm6(args.robot_ip, None, None) - - # Clear fault on the robot server if any - # if bestman.robot.isFault(): - # log.warn("Fault occurred on the robot server, trying to clear ...") - # bestman.robot.clearFault() - # time.sleep(2) - # if bestman.robot.isFault(): - # log.error("Fault cannot be cleared, exiting ...") - # return - # log.info("Fault on the robot server is cleared") - - # open gripper - bestman.gripper_goto(value=300, speed=8000, force=None) - - - except Exception as e: - # Log any exceptions that occur - print(str(e)) - - -if __name__ == "__main__": - main() diff --git a/Examples/teach_by_demonstration.py b/Examples/teach_by_demonstration.py deleted file mode 100644 index fed44e0..0000000 --- a/Examples/teach_by_demonstration.py +++ /dev/null @@ -1,192 +0,0 @@ -#!/usr/bin/env python - -"""intermediate4_teach_by_demonstration.py - -This tutorial shows a demo implementation for teach by demonstration: free-drive the robot and -record a series of Cartesian poses, which are then reproduced by the robot. - -Run this script using: -python intermediate4_teach_by_demonstration.py 192.168.2.100 192.168.2.108 -""" - -__copyright__ = "Copyright (C) 2016-2021 Flexiv Ltd. All Rights Reserved." -__author__ = "Flexiv" - -import time -import argparse -import sys -import os -parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) -sys.path.append(os.path.join(parent_dir, 'RoboticsToolBox')) -from utility import quat2eulerZYX -from utility import list2str -from utility import parse_pt_states -from Bestman_sim_flexiv import * - -# Maximum contact wrench [fx, fy, fz, mx, my, mz] [N][Nm] -MAX_CONTACT_WRENCH = [50.0, 50.0, 50.0, 15.0, 15.0, 15.0] - -def print_description(): - """ - Print tutorial description. - - """ - print( - "This tutorial shows a demo implementation for teach by demonstration: free-drive the " - "robot and record a series of Cartesian poses, which are then reproduced by the robot." - ) - print() - - -def main(): - # Program Setup - # ============================================================================================== - # Parse arguments - argparser = argparse.ArgumentParser() - argparser.add_argument("robot_ip", help="IP address of the robot server") - argparser.add_argument("local_ip", help="IP address of this PC") - args = argparser.parse_args() - - # Define alias - log = flexivrdk.Log() - mode = flexivrdk.Mode - - # Print description - log.info("Tutorial description:") - print_description() - - try: - # RDK Initialization - # ========================================================================================== - # Instantiate robot interface - robot = flexivrdk.Robot(args.robot_ip, args.local_ip) - - # Clear fault on robot server if any - if robot.isFault(): - log.warn("Fault occurred on robot server, trying to clear ...") - # Try to clear the fault - robot.clearFault() - time.sleep(2) - # Check again - if robot.isFault(): - log.error("Fault cannot be cleared, exiting ...") - return - log.info("Fault on robot server is cleared") - - # Enable the robot, make sure the E-stop is released before enabling - log.info("Enabling robot ...") - robot.enable() - - # Wait for the robot to become operational - while not robot.isOperational(): - time.sleep(1) - - log.info("Robot is now operational") - - # Teach By Demonstration - # ========================================================================================== - # Recorded robot poses - saved_poses = [] - - # Robot states data - robot_states = flexivrdk.RobotStates() - - # Acceptable user inputs - log.info("Accepted key inputs:") - print("[n] - start new teaching process") - print("[r] - record current robot pose") - print("[e] - finish recording and start execution") - - # User input polling - input_buffer = "" - while True: - input_buffer = str(input()) - # Start new teaching process - if input_buffer == "n": - # Clear storage - saved_poses.clear() - - # Put robot to plan execution mode - robot.setMode(mode.NRT_PLAN_EXECUTION) - - # Robot run free drive - robot.executePlan("PLAN-FreeDriveAuto") - - log.info("New teaching process started") - log.warn( - "Hold down the enabling button on the motion bar to activate free drive" - ) - # Save current robot pose - elif input_buffer == "r": - if not robot.isBusy(): - log.warn("Please start a new teaching process first") - continue - - robot.getRobotStates(robot_states) - saved_poses.append(robot_states.tcpPose) - log.info("New pose saved: " + str(robot_states.tcpPose)) - log.info("Number of saved poses: " + str(len(saved_poses))) - # Reproduce recorded poses - elif input_buffer == "e": - if len(saved_poses) == 0: - log.warn("No pose is saved yet") - continue - - # Put robot to primitive execution mode - robot.setMode(mode.NRT_PRIMITIVE_EXECUTION) - - for i in range(len(saved_poses)): - log.info( - "Executing pose " + str(i + 1) + "/" + str(len(saved_poses)) - ) - - target_pos = [ - saved_poses[i][0], - saved_poses[i][1], - saved_poses[i][2], - ] - # Convert quaternion to Euler ZYX required by MoveCompliance primitive - target_quat = [ - saved_poses[i][3], - saved_poses[i][4], - saved_poses[i][5], - saved_poses[i][6], - ] - - target_euler_deg = quat2eulerZYX(target_quat, degree=True) - robot.executePrimitive( - "MoveCompliance(target=" - + list2str(target_pos) - + list2str(target_euler_deg) - + "WORLD WORLD_ORIGIN, maxVel=0.3, enableMaxContactWrench=1, maxContactWrench=" - + list2str(MAX_CONTACT_WRENCH) - + ")" - ) - - # Wait for robot to reach target location by checking for "reachedTarget = 1" - # in the list of current primitive states - while ( - parse_pt_states(robot.getPrimitiveStates(), "reachedTarget") - != "1" - ): - time.sleep(1) - - log.info( - "All saved poses are executed, enter 'n' to start a new " - "teaching process, 'r' to record more poses, 'e' to repeat " - "execution" - ) - - # Put robot back to free drive - robot.setMode(mode.NRT_PLAN_EXECUTION) - robot.executePlan("PLAN-FreeDriveAuto") - else: - log.warn("Invalid input") - - except Exception as e: - # Print exception error message - log.error(str(e)) - - -if __name__ == "__main__": - main() diff --git a/Examples/test.py b/Examples/test.py new file mode 100644 index 0000000..c07643e --- /dev/null +++ b/Examples/test.py @@ -0,0 +1,85 @@ +# !/usr/bin/env python +# -*- encoding: utf-8 -*- +""" +# @FileName : move_arm_to_home.py +# @Time : 2024-12-01 15:21:45 +# @Author : Zhaxi & Yan +# @Email : zhaxizhuoma.ayang@gmail.com & yding25@binghamton.edu +# @Description : Move arm to default home position +# @Usage : python test.py 192.168.1.208 +""" + +import argparse +import time +from Robotics_API import Bestman_Real_Xarm6 +from scipy.spatial.transform import Rotation as R +import numpy as np +import math + +def main(): + # Parse Arguments + argparser = argparse.ArgumentParser( + description="Move the robot arm to follow a trajectory." + ) + # Required arguments + argparser.add_argument("robot_ip", help="IP address of the robot server") + args = argparser.parse_args() + + try: + # Instantiate the robot interface + bestman = Bestman_Real_Xarm6(args.robot_ip) + + # Initialize robot + if not bestman.initialize_robot(): + return # Exit if initialization fails + + # bestman.update_robot_states() + + # bestman.go_home() + # bestman.get_tcp_link() + # bestman.get_current_eef_pose() + + # Test + gt_pose = bestman.get_eef_state() + gt_joint = bestman.get_joint_state() + + # gt_joint[1] += 0.3 + # bestman.move_to_joint_angles(gt_joint) + + # gt_pose.position[2] += 0.3 + # bestman.move_to_eef_pose(gt_pose) + + # print(f'gt position:{gt_pose.position}, gt orientation:{gt_pose.orientation}') + # print(f'gt joint:{gt_joint}') + # # joint_values = [-1.6, -27.2, -2.2, -1.3, -60.6, 0.2] + # # joint_values_rad = [v / 180 * math.pi for v in joint_values] + # # joint_values_rad = [-0.12792803756892681, -0.47472870349884033, -0.038403209298849106, -0.022691410034894943, -1.0576682090759277, 0.0034917236771434546] + # # bestman.move_arm_to_joint_values(joint_values_rad, wait_for_finish=False) + + # # joint_values_rad = [-0.22792803756892681, -0.47472870349884033, -0.038403209298849106, -0.022691410034894943, -1.0576682090759277, 0.0034917236771434546] + # # bestman.move_arm_to_joint_values(joint_values_rad, wait_for_finish=False) + + # # pose.position[2] += 0.1 + # # print(f'test position:{pose.position}, test orientation:{pose.orientation}') + # # bestman.move_eef_to_goal_pose(pose) + + trans_joint = bestman.inverse_kinematics(gt_pose) + print(f'trans_joint:{trans_joint}') + bestman.set_joint_cmd(trans_joint) + + trans_pose = bestman.forward_kinematics(gt_joint) + print(f'trans_pose:{trans_pose}') + bestman.set_eef_cmd(trans_pose) + + bestman.close_gripper_robotiq() + bestman.open_gripper_robotiq() + bestman.set_gripper_pos_robotiq(100) + # print(bestman.get_gripper_position_robotiq()) + + except Exception as e: + # Log any exceptions that occur + print(str(e)) + + +if __name__ == "__main__": + main() diff --git a/Install/basic_environment.yaml b/Install/basic_environment.yaml index 4b39528..83aed21 100644 --- a/Install/basic_environment.yaml +++ b/Install/basic_environment.yaml @@ -1,4 +1,4 @@ -name: flexiv +name: xarm channels: - defaults dependencies: diff --git a/Install/install.md b/Install/install.md new file mode 100644 index 0000000..3a87b51 --- /dev/null +++ b/Install/install.md @@ -0,0 +1,49 @@ +# Installation + +## Install with conda (Linux) + +### Basic Env + +> ***Note**: This will only install the basic module. For other algorithm submodules, please follow the instructions [Algorithm Submodule Env](#algorithm-submodule-env) to install as needed.* + +1. Pull the repository and update the submodule +``` +cd /home/$(whoami) +git clone https://github.com/yding25/BestMan_Xarm.git +``` + +2. Install xArm SDK +``` +cd ~ +git clone https://github.com/xArm-Developer/xArm-Python-SDK.git +python3 setup.py install +``` + +3. Run the following script to add the project to the PYTHON search path + +``` +/home/$(whoami)/BestMan_Xarm/Install +chmod 777 pythonpath.sh +bash pythonpath.sh +source ~/.bashrc +``` + +4. Create basic conda environment +``` +cd /home/$(whoami)/BestMan_Xarm/Install +conda env create -f basic_environment.yaml +``` + +5. Install ROS environment (optional) +``` +cd /home/$(whoami)/BestMan_Flexiv/Install +chmod 777 install_ros_noetic.sh +bash install_ros_noetic.sh +source ~/.bashrc +``` + +6. Check ROS environment (optional) +``` +roscore +echo $ROS_DISTRO +``` diff --git a/Install/pythonpath.sh b/Install/pythonpath.sh new file mode 100755 index 0000000..430db39 --- /dev/null +++ b/Install/pythonpath.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# Add this project to the python search path +Install_dir=$(dirname "$(readlink -f "$0")") +project_dir=$(dirname "$Install_dir") +if [ -z "$PYTHONPATH" ] || ! echo "$PYTHONPATH" | grep -q "$project_dir"; then + PYTHONPATH="\$PYTHONPATH:$project_dir" +fi + +echo "export PYTHONPATH=$PYTHONPATH" >> ~/.bashrc + diff --git a/Install/xArm-Python-SDK b/Install/xArm-Python-SDK new file mode 160000 index 0000000..9f6ac54 --- /dev/null +++ b/Install/xArm-Python-SDK @@ -0,0 +1 @@ +Subproject commit 9f6ac54c121324ea1c8808b7884fc076211fab5a diff --git a/README.md b/README.md index c4bada1..48eb6ce 100644 --- a/README.md +++ b/README.md @@ -12,29 +12,20 @@ Welcome to the BestMan_Xarm repository, a codebase dedicated to the XArm 6 robotic arm. +## 🔥 News +- [2024-12-18] We update API names, such as "joint angles" -> "joint values", and revise some function "joints_to_cartesian", "cartesian_to_joints". -## 💻 Installation - -- (Optional) Create conda environment - - ``` - conda env create -f basic_environment.yaml - ``` - -- Install xArm-Python-SDK +### 🏠 Prerequisites - ```bash - cd ~ - git clone https://github.com/xArm-Developer/xArm-Python-SDK.git - python3 setup.py install - ``` +> ***Note**: We recommand Ubuntu 22.04 and python version deault to 3.8.* -- Clone the BestMan_Xarm Repository +- Ubuntu 22.04 +- Conda + - Python 3.8 - ```bash - git clone https://github.com/yding25/BestMan_Xarm.git - ``` +## 💻 Installation +We provide the installation guide [here](Install/install.md). You can install locally or use docker and verify the installation easily. ## 🔎 Project Structure @@ -52,14 +43,6 @@ firefox /home/$(whoami)/BestMan_Xarm/docs/html/index.html # API document cd ~/BestMan_Xarm/Examples/ # Demo: Go Home python3 move_arm_to_home.py 192.168.1.208 - -# Demo: Move Joints -python3 move_arm_to_joint_angles.py 192.168.1.208 - -# Demo: Move gripper -python3 close_gripper.py 192.168.1.208 -python3 open_gripper.py 192.168.1.208 -python3 open_gripper_width.py 192.168.1.208 ``` (We are fixing other demos.) diff --git a/RoboticsToolBox/Bestman_real_xarm6.py b/RoboticsToolBox/Bestman_real_xarm6.py deleted file mode 100644 index 12c0872..0000000 --- a/RoboticsToolBox/Bestman_real_xarm6.py +++ /dev/null @@ -1,562 +0,0 @@ -import numpy as np -import argparse -import time -import datetime -import ikpy -import serial -import serial.tools.list_ports -from ikpy.chain import Chain -from ikpy.inverse_kinematics import inverse_kinematic_optimization -from scipy.spatial.transform import Rotation as R -import sys -import os -import minimalmodbus as mm -import pyRobotiqGripper - -from xarm.wrapper import XArmAPI -current_dir = os.path.dirname(os.path.abspath(__file__)) - - -class Bestman_Real_Xarm6: - ''' - Functions for initalization - ''' - - def __init__(self, robot_ip, local_ip, frequency): - # Initialize the robot and gripper with the provided IPs and frequency - self.robot = XArmAPI(robot_ip) - local_ip = None - self.mode = self.robot.set_mode(0) # 0: default - self.robot_states = self.robot.set_state(0) - self.first_init_flag = True - self.gripper = True # have gripper by default - self.frequency = frequency - - # # URDF - # urdf_file = os.path.join(current_dir, "../Asset/xarm6_robot.urdf") - # self.robot_chain = Chain.from_urdf_file(urdf_file) - # self.active_joints = [ - # joint for joint in self.robot_chain.links - # if isinstance(joint, ikpy.link.URDFLink) and (joint.joint_type == 'revolute' or joint.joint_type == 'prismatic') - # ] - - ''' - Functions for xarm itself - ''' - def clear_fault(self): - '''Clear fault, and updates the current robot states.''' - self.robot.set_state(0) - - def update_robot_states(self): - '''Updates the current robot states.''' - self.robot.getRobotStates(self.robot_states) - - def set_mode(self, _mode): - ''' - Parameters: - _mode= - 0: position controlmode - 1: servo motionmode - 2: joint teachingmodemode (invalid)3: cartesian teaching - 4: joint velocity control mode - 5: cartesian velocity control mode - 6: joint online trajectory planningmode - 7: cartesian online trajectory planning - - Notes: - https://github.com/xArm-Developer/xArm-Python-SDK/blob/master/doc/api/xarm_api.md#mode - ''' - print("set mode") - self.robot.set_mode(_mode) - - def go_home(self,dist=0): - '''Move arm to initial pose.''' - - self.robot.set_position(x=396.4+dist, y=-5.5,z=360,roll=-90,pitch=-90,yaw=-90,wait=False) - time.sleep(3) - - def pose_to_euler(self, pose): - ''' - Convert robot pose from a list [x, y, z, qw, qx, qy, qz] to [x, y, z] and Euler angles. - - Parameters: - pose: list of 7 floats - [x, y, z, qw, qx, qy, qz] - - Returns: - tuple: (x, y, z, roll, pitch, yaw) where (x, y, z) is the position and (roll, pitch, yaw) are the Euler angles in radians. - ''' - x, y, z, qw, qx, qy, qz = pose - r = R.from_quat([qx, qy, qz, qw]) # Reordering to match scipy's [qx, qy, qz, qw] - roll, pitch, yaw = r.as_euler('xyz', degrees=False) - return [x, y, z, roll, pitch, yaw] - - def euler_to_pose(self, position_euler): - ''' - Convert robot pose from [x, y, z, roll, pitch, yaw] to [x, y, z, qw, qx, qy, qz]. - - Parameters: - position_euler: list of 6 floats - [x, y, z, roll, pitch, yaw] - - Returns: - list: [x, y, z, qw, qx, qy, qz] - ''' - x, y, z, roll, pitch, yaw = position_euler - r = R.from_euler('xyz', [roll, pitch, yaw], degrees=False) - qx, qy, qz, qw = r.as_quat() # Getting [qx, qy, qz, qw] from scipy - return [x, y, z, qw, qx, qy, qz] # Reordering to match [qw, qx, qy, qz] - - def set_pay_load(self,payload_weight, payload_cog): - - self.robot.set_tcp_load(payload_weight, payload_cog) - - - # ---------------------------------------------------------------- - # Functions for others - # ---------------------------------------------------------------- - - def get_joint_bounds(self): - ''' - Retrieves the joint bounds of the robot arm. - - Returns: - list: A list of tuples representing the joint bounds, where each tuple contains the minimum and maximum values for a joint. - ''' - maxbounds = self.robot.info().qMax - minbounds = self.robot.info().qMin - jointbounds = list(zip(maxbounds,minbounds)) - return jointbounds - - - def get_DOF(self): - ''' - Retrieves the degree of freedom (DOF) of the robot arm. - - Returns: - int: The degree of freedom of the robot arm. - ''' - return 6 - - # ---------------------------------------------------------------- - # Functions for joint control - # ---------------------------------------------------------------- - - def print_joint_link_info(self, name): - ''' - Prints the joint and link information of a robot. - - Args: - name (str): 'base' or 'arm' - ''' - if name == 'base': - print("Base joint and link information:") - for i, link in enumerate(self.robot_chain.links[:1]): # Assuming the base is the first link - print(f"Link {i}: {link.name}") - elif name == 'arm': - print("Arm joint and link information:") - for i, link in enumerate(self.robot_chain.links[1:]): # Assuming the arm starts from the second link - print(f"Link {i + 1}: {link.name}") - - def get_joint_idx(self): - ''' - Retrieves the indices of the joints in the robot arm. - - Returns: - list: A list of indices for the joints in the robot arm. - ''' - return list(range(len(self.active_joints))) - - def get_tcp_link(self): - ''' - Retrieves the TCP (Tool Center Point) link of the robot arm. - - Returns: - str: The TCP link of the robot arm. - ''' - return self.robot_chain.links[6].name - - def get_current_joint_angles(self): - ''' - Retrieves the current joint angles of the robot arm. - - Returns: - list: A list of the current joint angles of the robot arm. - ''' - _joint_states = self.robot.get_joint_states(is_radian=True) - _joint_angles = _joint_states[1][0][0:6] - - return _joint_angles - - def get_current_joint_velocities(self): - ''' - Retrieves the current joint velocities of the robot arm. - - Returns: - list: A list of the current joint velocities of the robot arm. - ''' - - _joint_states = self.robot.get_joint_states(is_radian=True) - _joint_velocities = _joint_states[1][1][0:6] - - return _joint_velocities - - - - def move_arm_to_joint_angles(self, joint_angles, target_vel=None, target_acc=None, MAX_VEL=None, MAX_ACC=None, wait_for_finish=None): - ''' - Move arm to a specific set of joint angles, considering physics. - - Args: - Set the servo angle, the API will modify self.last_used_angles value - Note: -     https://github.com/xArm-Developer/xArm-Python-SDK/blob/master/doc/api/xarm_api.md#def-set_servo_angleself-servo_idnone-anglenone-speednone-mvaccnone-mvtimenone-relativefalse-is_radiannone-waitfalse-timeoutnone-radiusnone-kwargs - ''' - - #! Force mode switch - self.robot.set_mode(6) # 6: online joint - self.robot.set_state(0) - - self.robot.set_servo_angle(angle=joint_angles, is_radian=True, speed=0.7, wait=wait_for_finish) # speed in rad/s - - def move_arm_follow_joint_angles(self, target_trajectory, target_vel=None, target_acc=None, MAX_VEL=None, MAX_ACC=None): - ''' - Move arm to a few set of joint angles, considering physics. - - Args: - target_trajectory: A list of desired joint angles (in radians) for each joint of the arm. - target_vel: Optional. A list of target velocities for each joint. - target_acc: Optional. A list of target accelerations for each joint. - MAX_VEL: Optional. A list of maximum velocities for each joint. - MAX_ACC: Optional. A list of maximum accelerations for each joint. - ''' - period = 1.0 / self.frequency - self.update_robot_states() - DOF = len(self.robot_states.q) - - for target_pos in target_trajectory: - # Monitor fault on robot server - if self.robot.isFault(): - raise Exception("Fault occurred on robot server, exiting ...") - - # Send command - self.robot.set_servo_angle(angle=target_pos, is_radian=True, speed=target_vel, wait=True) # speed in rad/s - print(f"Sent joint positions: {target_pos}") - - # Use sleep to control loop period - time.sleep(period) - - # ---------------------------------------------------------------- - # Functions for end effector - # ---------------------------------------------------------------- - - # TODO - def get_current_tcp_speed(self): - ''' - Retrieves the current tcp velocities of the robot arm. - - Returns: - list: A list of the current tcp velocities of the robot arm. - ''' - speed = self.robot.realtime_tcp_speed - return speed - - - def get_current_end_effector_pose(self): - ''' - Retrieves the current pose of the robot arm's end effector. - - This function obtains the position and orientation of the end effector. - - Returns: - pose: the [x, y, z, roll, pitch, yaw] value of tcp in meter and radian - ''' - - _pose = self.robot.get_position(is_radian=True) - pose = _pose[1] - pose[0] = pose[0] / 1000 - pose[1] = pose[1] / 1000 - pose[2] = pose[2] / 1000 - return pose - - - - def move_end_effector_to_tcp_velocity(self, _velocity_setpoint, _duration): - ''' - Move arm's end effector to a target tcp velocity. - - Args: - _velocity_setpoint: mm/s in transition, rad/s in orientation - _duration: function calling loop time - Notes: - https://github.com/xArm-Developer/xArm-Python-SDK/blob/master/doc/api/xarm_api.md#def-vc_set_cartesian_velocityself-speeds-is_radiannone-is_tool_coordfalse-duration-1-kwargs - ''' - - self.robot.set_mode(5) # 5 for cartesian vel - self.robot.set_state(0) - self.robot.vc_set_cartesian_velocity(speeds=[_velocity_setpoint[0], - _velocity_setpoint[1], - _velocity_setpoint[2], - _velocity_setpoint[3], - _velocity_setpoint[4], - _velocity_setpoint[5]], - duration=_duration) - - def move_end_effector_to_goal_pose(self, end_effector_goal_pose, speed=1000, mvacc=50000, wait=False): - ''' - Move arm's end effector to a target position. - - Args: - end_effector_goal_pose (Pose): The desired pose of the end effector (includes both position in mm and euler_orientation), please use radian for orientaiton. - ''' - - self.robot.set_position(x=end_effector_goal_pose[0]*1000 , y=end_effector_goal_pose[1]*1000,z=end_effector_goal_pose[2]*1000, - roll=end_effector_goal_pose[3],pitch=end_effector_goal_pose[4],yaw=end_effector_goal_pose[5], speed=speed, mvacc=mvacc, is_radian=True, wait=wait) - - - - def rotate_end_effector_joint(self, angle): - ''' - Rotate the end effector of the robot arm by a specified angle by joint. - - Args: - angle (float): The desired rotation angle in radians. - ''' - current_joint_angles = self.get_current_joint_angles() - - target_joint_angles = current_joint_angles.copy() - target_joint_angles[6] += angle - target_vel = 1 - self.robot.set_mode(6) # 0: joint control mode; 6: online joint - self.robot.set_state(0) - - self.robot.set_servo_angle(angle=target_joint_angles, is_radian=True, speed=target_vel, wait=False) # speed in rad/s - - # ---------------------------------------------------------------- - # Functions for IK - # ---------------------------------------------------------------- - - def joints_to_cartesian(self, joint_angles): - ''' - Transforms the robot arm's joint angles to its Cartesian coordinates. - - Args: - joint_angles (list): A list of joint angles for the robot arm. - - Returns: - tuple: A tuple containing the Cartesian coordinates (position and orientation) of the robot arm. - ''' - # Validate the number of joint values matches the number of active joints - if len(joint_angles) != len(self.active_joints): - raise ValueError("The number of joint values does not match the number of active joints") - - # Map joint values to the full joint chain - full_joint_angles = np.zeros(len(self.robot_chain.links)) - active_joint_indices = [self.robot_chain.links.index(joint) for joint in self.active_joints] - - for i, joint_value in enumerate(joint_angles): - full_joint_angles[active_joint_indices[i]] = joint_value - - # Calculate the end effector position and orientation - cartesian_matrix = self.robot_chain.forward_kinematics(full_joint_angles) - - # Extract position and orientation - position = cartesian_matrix[:3, 3] - orientation_matrix = cartesian_matrix[:3, :3] - - # Convert rotation matrix to quaternion - r = R.from_matrix(orientation_matrix) - quaternion = r.as_quat() - orientation = [quaternion[3], quaternion[0], quaternion[1], quaternion[2]] - - return position, orientation - - def cartesian_to_joints(self, position, orientation): - ''' - Transforms the robot arm's Cartesian coordinates to its joint angles. - - Args: - position (list): The Cartesian position of the robot arm. - orientation (list): The Cartesian orientation of the robot arm. - - Returns: - list: A list of joint angles corresponding to the given Cartesian coordinates. - ''' - rotation_matrix = R.from_euler('xyz', orientation).as_matrix() - - # Combine rotation matrix and position into a list - target_pose = np.eye(4) - target_pose[:3, :3] = rotation_matrix - target_pose[:3, 3] = position - - initial_joint_angles = [0] * len(self.robot_chain) - - # inverse kinematics calculations and return joint angles - joint_angles = ikpy.inverse_kinematics.inverse_kinematic_optimization( - chain=self.robot_chain, - target_frame=target_pose, - starting_nodes_angles=initial_joint_angles, - orientation_mode='all', - ) - - return joint_angles[1:8] - - - def calculate_IK_error(self, goal_position, goal_orientation): - ''' - Calculate the inverse kinematics (IK) error for performing pick-and-place manipulation of an object using a robot arm. - - Args: - goal_position: The desired goal position for the target object. - goal_orientation: The desired goal orientation for the target object. - ''' - pass - - # ---------------------------------------------------------------- - # Functions for gripper - # ---------------------------------------------------------------- - - def find_gripper_xarm(self): - ''' - Searches for the gripper on available serial ports and returns the port if found. - - Returns: - str: The serial port where the gripper is connected, or None if not found. - ''' - _pos = self.robot.get_gripper_position() - _ver = self.robot.get_gripper_version() - - if _ver is not None and _pos is not None: - print("Have Xarm gripper", _ver) - return True - else: - print("Not found Xarm gripper") - return None - - - def find_gripper_robotiq(self): - """ - Config the parameter via Python SDK - """ - # Baud rate - # Modify the baud rate to 115200, the default is 2000000. - self.robot.set_tgpio_modbus_baudrate(115200) - - # TCP Payload and offset - # Robotiq 2F/85 Gripper - self.robot.set_tcp_load(0.925, [0, 0, 58]) - self.robot.set_tcp_offset([0, 0, 174, 0, 0, 0]) - self.robot.save_conf() - - # Self-Collision Prevention Model - # Robotiq 2F/85 Gripper - self.robot.set_collision_tool_model(4) - - self.robot.robotiq_reset() - self.robot.robotiq_set_activate() #enable the robotiq gripper - - - def get_gripper_position_xarm(self): - ''' - Get the position of the XArm gripper. - ''' - gripper_pos = self.robot.get_gripper_position() - - return gripper_pos[1] - - - def get_gripper_position_robotiq(self, number_of_registers=3): - """ - Reading the status of robotiq gripper - - :param number_of_registers: number of registers, 1/2/3, default is 3 - number_of_registers=1: reading the content of register 0x07D0 - number_of_registers=2: reading the content of register 0x07D0/0x07D1 - number_of_registers=3: reading the content of register 0x07D0/0x07D1/0x07D2 - - Note: - register 0x07D0: Register GRIPPER STATUS - register 0x07D1: Register FAULT STATUS and register POSITION REQUEST ECHO - register 0x07D2: Register POSITION and register CURRENT - :return: tuple((code, robotiq_response)) - code: See the [API Code Documentation](./xarm_api_code.md#api-code) for details. - robotiq_response: See the robotiq documentation - """ - status = self.robot.robotiq_get_status(number_of_registers=number_of_registers) - gripper_width = status[1][-2] - return gripper_width - - - - def gripper_goto_xarm(self, value, speed=5000, force=None): - ''' - Moves the gripper to a specified position with given speed. - - Args: - value (int): Position of the gripper. Integer between 0 and 800. - 0 represents the open position, and 255 represents the closed position. - speed (int): Speed of the gripper movement, between 0 and 8000. - force (int): Not applicable for xarm gripper - - Note: - - 0 means fully closed. - - 800 means fully open. - ''' - self.robot.set_gripper_position(pos=value, speed=speed, wait=False, timeout=1, auto_enable=True) - - - def gripper_goto_robotiq(self, pos, speed=0xFF, force=0xFF, wait=False, timeout=5, **kwargs): - """ - Go to the position with determined speed and force. - - :param pos: position of the gripper. Integer between 0 and 255. 0 being the open position and 255 being the close position. - :param speed: gripper speed between 0 and 255 - :param force: gripper force between 0 and 255 - :param wait: whether to wait for the robotion motion complete, default is True - :param timeout: maximum waiting time(unit: second), default is 5, only available if wait=True - - :return: tuple((code, robotiq_response)) - code: See the [API Code Documentation](./xarm_api_code.md#api-code) for details. - robotiq_response: See the robotiq documentation - """ - return self.robot.robotiq_set_position(pos, speed=speed, force=force, wait=wait, timeout=timeout, **kwargs) - - - def open_gripper_xarm(self): - ''' Opens the gripper to its maximum position with maximum speed and force. ''' - self.gripper_goto(value=850, speed=5000, force=None) - - - def open_gripper_robotiq(self, speed=0xFF, force=0xFF, wait=False, timeout=5, **kwargs): - """ - Open the robotiq gripper - - :param speed: gripper speed between 0 and 255 - :param force: gripper force between 0 and 255 - :param wait: whether to wait for the robotiq motion to complete, default is True - :param timeout: maximum waiting time(unit: second), default is 5, only available if wait=True - - :return: tuple((code, robotiq_response)) - code: See the [API Code Documentation](./xarm_api_code.md#api-code) for details. - robotiq_response: See the robotiq documentation - """ - return self.robot.robotiq_open(speed=speed, force=force, wait=wait, timeout=timeout, **kwargs) - - - def close_gripper_xarm(self): - '''Closes the gripper to its minimum position with maximum speed and force.''' - self.gripper_goto(value=0, speed=5000, force=None) - - - def close_gripper_robotiq(self, speed=0xFF, force=0xFF, wait=False, timeout=5, **kwargs): - """ - Close the robotiq gripper - - :param speed: gripper speed between 0 and 255 - :param force: gripper force between 0 and 255 - :param wait: whether to wait for the robotiq motion to complete, default is True - :param timeout: maximum waiting time(unit: second), default is 3, only available if wait=True - - :return: tuple((code, robotiq_response)) - code: See the [API Code Documentation](./xarm_api_code.md#api-code) for details. - robotiq_response: See the robotiq documentation - """ - return self.robot.robotiq_close(speed=speed, force=force, wait=wait, timeout=timeout, **kwargs) \ No newline at end of file diff --git a/RoboticsToolBox/__pycache__/Bestman_real_xarm6.cpython-38.pyc b/RoboticsToolBox/__pycache__/Bestman_real_xarm6.cpython-38.pyc deleted file mode 100644 index 390b55a..0000000 Binary files a/RoboticsToolBox/__pycache__/Bestman_real_xarm6.cpython-38.pyc and /dev/null differ diff --git a/RoboticsToolBox/__pycache__/Bestman_sim_flexiv.cpython-38.pyc b/RoboticsToolBox/__pycache__/Bestman_sim_flexiv.cpython-38.pyc deleted file mode 100644 index 03e5e68..0000000 Binary files a/RoboticsToolBox/__pycache__/Bestman_sim_flexiv.cpython-38.pyc and /dev/null differ diff --git a/RoboticsToolBox/__pycache__/utility.cpython-38.pyc b/RoboticsToolBox/__pycache__/utility.cpython-38.pyc deleted file mode 100644 index fbcfa12..0000000 Binary files a/RoboticsToolBox/__pycache__/utility.cpython-38.pyc and /dev/null differ diff --git a/RoboticsToolBox/utility.py b/RoboticsToolBox/utility.py deleted file mode 100644 index 192bf9b..0000000 --- a/RoboticsToolBox/utility.py +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env python - -"""utility.py - -Utility methods. -""" - -__copyright__ = "Copyright (C) 2016-2021 Flexiv Ltd. All Rights Reserved." -__author__ = "Flexiv" - -import math -# pip install scipy -from scipy.spatial.transform import Rotation as R - - -def quat2eulerZYX(quat, degree=False): - """ - Convert quaternion to Euler angles with ZYX axis rotations. - - Parameters - ---------- - quat : float list - Quaternion input in [w,x,y,z] order. - degree : bool - Return values in degrees, otherwise in radians. - - Returns - ---------- - float list - Euler angles in [x,y,z] order, radian by default unless specified otherwise. - """ - - # Convert target quaternion to Euler ZYX using scipy package's 'xyz' extrinsic rotation - # NOTE: scipy uses [x,y,z,w] order to represent quaternion - eulerZYX = R.from_quat([quat[1], quat[2], - quat[3], quat[0]]).as_euler('xyz', degrees=degree).tolist() - - return eulerZYX - - -def list2str(ls): - """ - Convert a list to a string. - - Parameters - ---------- - ls : list - Source list of any size. - - Returns - ---------- - str - A string with format "ls[0] ls[1] ... ls[n] ", i.e. each value - followed by a space, including the last one. - """ - - ret_str = "" - for i in ls: - ret_str += str(i) + " " - return ret_str - - -def parse_pt_states(pt_states, parse_target): - """ - Parse the value of a specified primitive state from the pt_states string list. - - Parameters - ---------- - pt_states : str list - Primitive states string list returned from Robot::getPrimitiveStates(). - parse_target : str - Name of the primitive state to parse for. - - Returns - ---------- - str - Value of the specified primitive state in string format. Empty string is - returned if parse_target does not exist. - """ - for state in pt_states: - # Split the state sentence into words - words = state.split() - - if words[0] == parse_target: - return words[-1] - - return "" diff --git a/Robotics_API/Bestman_Real_Xarm6.py b/Robotics_API/Bestman_Real_Xarm6.py new file mode 100644 index 0000000..ea7d1bd --- /dev/null +++ b/Robotics_API/Bestman_Real_Xarm6.py @@ -0,0 +1,717 @@ +# !/usr/bin/env python +# -*- encoding: utf-8 -*- +""" +# @FileName : Bestman_real_xarm6.py +# @Time : 2024-12-17 22:22:23 +# @Author : Zhaxi & Yan +# @Email : zhaxizhuoma.ayang@gmail.com & yding25@binghamton.edu +# @Description : XXX +# @Usage : XXX +""" + + +import os +import sys +import time +import numpy as np +from .Pose import Pose +from scipy.spatial.transform import Rotation as R +import ikpy +from ikpy.chain import Chain +from ikpy.link import URDFLink +from xarm.wrapper import XArmAPI +import logging + + +class Bestman_Real_Xarm6: + """ + A class to interface with the XArm6 robotic arm. + + Attributes: + robot: The XArm6 API object initialized with the provided IP address. + frequency: The control frequency for the robot. + log: A logger object to log messages for debugging and monitoring. + robot_states: A container for storing the current state of the robot. + mode: Stores the current operation mode. + gripper: The gripper object for controlling the end effector. + """ + + def __init__(self, robot_ip, local_ip=None, frequency=None): + # DONE + """ + Initializes the xArm robot and related components. + + Args: + robot_ip (str): The IP address of the xArm robot. + """ + # Robot initialization + self.robot = XArmAPI(robot_ip) + self.gripper = None + self.local_ip = local_ip + self.frequency = frequency + self.robot.set_mode(0) # Default mode + self.robot.set_tgpio_modbus_baudrate(115200) + self.robot.set_tcp_load(1.917, [9.12, -2.28, 85.08]) + self.robot.set_tcp_offset([0, 0, 318.1, 0, 0, 0]) + self.robot.save_conf() + + # # Parse URDF for kinematic chain + # current_dir = os.path.dirname(os.path.abspath(__file__)) + # urdf_file = os.path.join(current_dir, "../Asset/xarm6_robot.urdf") + # if not os.path.exists(urdf_file): + # raise FileNotFoundError(f"URDF file not found: {urdf_file}") + # self.robot_chain = Chain.from_urdf_file(urdf_file, base_elements=["world"]) # Parse the URDF file and configure the kinematic chain + + # # Define active links mask for xArm6 (Base + 6 DOF + End Effector) + # active_links_mask = [False] + [False] + [True] * 6 # Adjust for 6-DOF robot + # self.robot_chain.active_links_mask = active_links_mask + # self.active_joints = [ + # joint + # for joint in self.robot_chain.links + # if isinstance(joint, URDFLink) + # and (joint.joint_type == "revolute" or joint.joint_type == "prismatic") + # ] + + # # Validate the number of active joints for xArm6 + # if len(self.active_joints) != 6: # xArm6 is a 6-DOF robot + # raise ValueError( + # f"Expected 6 active joints, but found {len(self.active_joints)}. " + # f"Check the URDF file and active_links_mask." + # ) + + # Configure the logger (simplified setup) + logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(levelname)s - %(message)s", + handlers=[ + logging.StreamHandler(), # Output to console + # logging.FileHandler("xarm_command_log.log"), # Output to file + ], + ) + self.log = logging.getLogger("XArmRobotController") + + # ---------------------------------------------------------------- + # Device Initialization and State Management + # ---------------------------------------------------------------- + + def initialize_robot(self): + # DONE + """ + Initializes the robot by clearing faults and enabling it. + + Returns: + bool: True if the robot is successfully initialized, False otherwise. + """ + try: + self.log.info("Checking for faults on the robot...") + self.robot.clean_error() + self.robot.clean_warn() + time.sleep(2) + self.log.info(f"Successfully initialize the robot") + return True + + except Exception as e: + self.log.error(f"Failed to initialize the robot: {str(e)}") + return False + + def update_robot_states(self): + # DONE + """ + Fetches and returns the current robot states (joint angles and TCP pose in quaternion format). + """ + try: + # Retrieve joint states + joint_states = self.robot.get_joint_states(is_radian=True) + joint_values = joint_states[1][0][:6] + joint_velocities = joint_states[1][1][:6] # TODO + + # Retrieve end effector pose (Euler angles) + success, tcp_pose = self.robot.get_position(is_radian=True) + if success != 0: + raise ValueError("Failed to retrieve end-effector pose") + + position = [value / 1000 for value in tcp_pose[:3]] # x, y, z in meters # ! + orientation = R.from_euler('xyz', tcp_pose[3:6]).as_quat() # ! + + robot_states = { + "joint_values": joint_values, + "tcp_pose (quaternion)": { + "position": position, # Position in meters + "orientation": orientation.tolist(), # Quaternion (x, y, z, w) + }, + } + + # Log and display TCP pose in quaternion format + self.log.info(f"Robot states updated: {robot_states}") + + # Return the robot states + return robot_states + + except Exception as e: + self.log.error(f"Error updating robot states: {str(e)}") + + def reset_to_home(self): + # DONE + """ + Moves the robot arm to its initial (home) pose. + Converts input position (in meters) to millimeters for the xArm API, + and quaternion orientation to Euler angles. + """ + try: + # Set robot to online tcp control mode + if self.robot.set_mode(0) != 0: + self.log.error( + "Failed to set robot mode to 'online joint control mode'." + ) + return + if self.robot.set_state(0) != 0: + self.log.error("Failed to set robot state to operational.") + return + + position = [0.420, -0.0055, 0.36] + orientation = [0.7071, -0.0006, 0.7071, 0.0006] + pose = Pose(position, orientation) + orientation = pose.get_orientation(type="euler") + + # Command the robot to move to the home pose + self.robot.set_position( + x=position[0] * 1000, + y=position[1] * 1000, + z=position[2] * 1000, + roll=orientation[0], + pitch=orientation[1], + yaw=orientation[2], + is_radian=True + ) + self.log.info(f"Robot moved to home position") + except Exception as e: + self.log.error(f"Cannot go home: {str(e)}") + + + def set_payload(self, payload_weight, payload_cog): + # TODO + self.robot.set_tcp_load(payload_weight, payload_cog) + + # ---------------------------------------------------------------- + # Arm Functions + # ---------------------------------------------------------------- + def get_joint_bounds(self): + # TODO + """ + Retrieves the joint limits of the xArm6 robot arm. + + Returns: + list: A list of tuples, where each tuple contains the minimum and maximum value for each joint. + """ + joint_min = [-3.14, -1.57, -3.14, -1.57, -3.14, -1.57] + joint_max = [3.14, 2.09, 3.14, 2.09, 3.14, 2.09] + + return list(zip(joint_min, joint_max)) + + def print_links_info(self): + # TODO + """ + Prints the joint and link information of the robot's arm. + + Returns: + None + """ + print("Arm joint and link information:") + for i, link in enumerate(self.robot_chain.links): + print(f"Link {i} attributes: {vars(link)}") + + def get_joint_idx(self): + # TODO + """ + Retrieves the indices of the joints in the robot arm. + + Returns: + list: A list of indices for the joints in the robot arm. + """ + return list(range(len(self.active_joints))) + + def get_dof(self): + # DONE + """ + Retrieves the degree of freedom (DOF) of the xArm robot. + + Returns: + int: The number of degrees of freedom. + """ + dof = 6 # xArm6 is a 6-DOF robotic arm + self.log.info(f"Robot DOF: {dof}") + return dof + + def get_links_info(self): + # TODO + """ + Retrieves the name of the TCP (Tool Center Point) link. + + Returns: + str: Name of the TCP link. + """ + try: + tcp_link_name = self.robot_chain.links[-1].name + self.log.info(f"TCP link retrieved: {tcp_link_name}") + return tcp_link_name + except IndexError as e: + self.log.error(f"Failed to retrieve TCP link: {str(e)}") + return None + + def get_joint_state(self): + # DONE + """ + Retrieves the current joint angles of the robot arm. + + Returns: + list[float]: A list of the current joint angles of the robot arm (in radians). + """ + try: + # Get joint states and extract joint angles for the first 6 joints + joint_states = self.robot.get_joint_states(is_radian=True) + joint_values = joint_states[1][0][:6] + self.log.info(f"Current joint angles: {joint_values}") + return joint_values + except Exception as e: + self.log.error(f"Failed to retrieve current joint angles: {str(e)}") + return [] + + def get_joint_vel(self): + """ + Retrieves the current joint velocities of the robot arm. + + Returns: + list[float]: A list of the current joint velocities of the robot arm (in radians per second). + """ + try: + # Get joint states and extract joint velocities for the first 6 joints + joint_states = self.robot.get_joint_states(is_radian=True) + joint_velocities = joint_states[1][1][:6] + self.log.info(f"Current joint velocities: {joint_velocities}") + return joint_velocities + except Exception as e: + self.log.error(f"Failed to retrieve current joint velocities: {str(e)}") + return [] + + def get_eef_state(self): + # DONE + """ + Retrieves the current pose of the robot arm's end effector. + + Returns: + Pose: A Pose object representing the end effector's position and orientation. + """ + try: + success, raw_pose = self.robot.get_position(is_radian=True) + if success != 0: + raise ValueError( + f"Failed to retrieve end effector pose, error code: {success}" + ) + # Convert position values from millimeters to meters + position = [ + raw_pose[0] / 1000, + raw_pose[1] / 1000, + raw_pose[2] / 1000, + ] + orientation = raw_pose[3:] + pose = Pose(position, orientation) + self.log.info(f"Current end effector pose: {pose.position} and {pose.orientation}") + return pose + except Exception as e: + self.log.error(f"Failed to retrieve end effector pose: {str(e)}") + return None + + def set_joint_cmd(self, joint_values, target_vel=None, target_acc=None, max_vel=None, max_acc=None): + # DONE + """ + Moves the robotic arm to a specific set of joint values. + + Args: + joint_values (list[float]): Target joint values in radians. + """ + try: + # Set robot to online joint control mode + if self.robot.set_mode(6) != 0: + self.log.error( + "Failed to set robot mode to 'online joint control mode'." + ) + return + if self.robot.set_state(0) != 0: + self.log.error("Failed to set robot state to operational.") + return + + # Execute joint angle command + result = self.robot.set_servo_angle( + angle=joint_values, + is_radian=True, + speed=0.7, # Fixed speed in radians per second + wait=False, # Disable this function + ) + + if result != 0: + self.log.error( + f"Failed to move arm to joint angles: {joint_values}. Error code: {result}" + ) + else: + self.log.info(f"Moving arm to joint angles: {joint_values}") + + except Exception as e: + self.log.error( + f"An error occurred while moving arm to joint angles: {str(e)}" + ) + + def get_eef_vel(self): + # TODO + """ + Retrieves the current tcp velocities of the robot arm. + + Returns: + list: A list of the current tcp velocities of the robot arm. + """ + speed = self.robot.realtime_tcp_speed + return speed + + # ---------------------------------------------------------------- + # End Effector (EEF) Functions + # ---------------------------------------------------------------- + + def set_eef_cmd(self, goal_pose, max_linear_vel=100, max_angular_vel=5000): + """ + Move arm's end effector to a target position. + + Args: + goal_pose (Pose): The desired pose of the end effector (includes both position in mm and euler_orientation), please use radian for orientaiton. + """ + try: + # Set robot to online joint control mode + if self.robot.set_mode(7) != 0: + self.log.error( + "Failed to set robot mode to 'online joint control mode'." + ) + return + if self.robot.set_state(0) != 0: + self.log.error("Failed to set robot state to operational.") + return + + if not isinstance(goal_pose, Pose): + raise TypeError("pose must be an instance of Pose") + + position = goal_pose.get_position() + orientation = goal_pose.get_orientation(type="euler") + + self.robot.set_position( + x=position[0] * 1000, + y=position[1] * 1000, + z=position[2] * 1000, + roll=orientation[0], + pitch=orientation[1], + yaw=orientation[2], + speed=max_linear_vel, + mvacc=max_angular_vel, + is_radian=True, # ! + wait=False, # ! + ) + self.log.info(f"Moved end effector to pose: {goal_pose.position}, {goal_pose.orientation}") + + except Exception as e: + self.log.error(f"Failed to move end effector to goal pose: {str(e)}") + + def set_eef_vel(self, _velocity_setpoint, _duration): + #TODO + """ + Move arm's end effector to a target tcp velocity. + + Args: + _velocity_setpoint: mm/s in transition, rad/s in orientation + _duration: function calling loop time + Notes: + https://github.com/xArm-Developer/xArm-Python-SDK/blob/master/doc/api/xarm_api.md#def-vc_set_cartesian_velocityself-speeds-is_radiannone-is_tool_coordfalse-duration-1-kwargs + """ + + self.robot.set_mode(5) # 5 for cartesian vel + self.robot.set_state(0) + self.robot.vc_set_cartesian_velocity( + speeds=[ + _velocity_setpoint[0], + _velocity_setpoint[1], + _velocity_setpoint[2], + _velocity_setpoint[3], + _velocity_setpoint[4], + _velocity_setpoint[5], + ], + duration=_duration, + ) + + def rot_eef_joint(self, axis, angle): + # TODO + """ + Rotate the end effector of the robot arm by a specified angle by joint. + + Args: + angle (float): The desired rotation angle in radians. + """ + current_joint_values = self.get_current_joint_values() + + target_joint_values = current_joint_values.copy() + target_joint_values[6] += angle + target_vel = 1 + self.robot.set_mode(6) # 0: joint control mode; 6: online joint + self.robot.set_state(0) + + self.robot.set_servo_angle( + angle=target_joint_values, is_radian=True, speed=target_vel, wait=False + ) # speed in rad/s + + # ---------------------------------------------------------------- + # Functions for IK + # ---------------------------------------------------------------- + def forward_kinematics(self, joint_values): + # DONE + """ + Converts the robot's joint angles to its Cartesian coordinates. + + Args: + joint_values (list[float]): A list of joint angles for the robot arm. + + Returns: + Pose: A Pose object representing the Cartesian position and quaternion orientation. + + Raises: + ValueError: If the number of joint values does not match the number of active joints. + """ + try: + if len(joint_values) != len(self.active_joints): + raise ValueError( + "The number of joint values does not match the number of active joints." + ) + + full_joint_values = np.zeros(len(self.robot_chain.links)) + active_joint_indices = [ + self.robot_chain.links.index(joint) for joint in self.active_joints + ] + + for i, joint_value in enumerate(joint_values): + full_joint_values[active_joint_indices[i]] = joint_value + + cartesian_matrix = self.robot_chain.forward_kinematics(full_joint_values) + position = cartesian_matrix[:3, 3] + orientation_matrix = cartesian_matrix[:3, :3] + quaternion = R.from_matrix(orientation_matrix).as_quat() + + # Add the gripper length along the end-effector's Z-axis + gripper_length = 0.17 # 17cm; adjust as needed + gripper_offset = gripper_length * orientation_matrix[:, 2] # Z-axis of the end-effector + adjusted_position = position + gripper_offset + + self.log.info(f"Converted joint values {joint_values} to Cartesian Pose {position} and {quaternion}.") + return Pose(adjusted_position, quaternion) + except Exception as e: + self.log.error(f"Error converting joint values to Cartesian: {str(e)}") + raise + + def inverse_kinematics(self, pose, initial_joint_values=None): + # DONE + """ + Converts the robot's Cartesian coordinates to its joint angles. + + Args: + position (list[float]): Cartesian position of the robot arm. + orientation (list[float]): Quaternion orientation of the robot arm. + + Returns: + list[float]: Joint angles corresponding to the given Cartesian coordinates. + + Raises: + ValueError: If the solution is invalid or out of bounds. + """ + try: + orientation = pose.orientation + rotation = R.from_quat(orientation) # qx, qy, qz, qw + rotation_matrix = rotation.as_matrix() + + target_pose = np.eye(4) + target_pose[:3, :3] = rotation_matrix + # Adjust position by subtracting the gripper length along the end-effector Z-axis + gripper_length = 0.17 # Gripper length in meters (15cm open, 17cm closed) + target_pose[:3, 3] = pose.position - gripper_length * rotation_matrix[:, 2] + + if initial_joint_values is None: + initial_joint_values = [0, 0] + self.get_current_joint_values() # 不同的urdf,[0]位置不一样 + + joint_values = ikpy.inverse_kinematics.inverse_kinematic_optimization( + chain=self.robot_chain, + target_frame=target_pose, + starting_nodes_angles=initial_joint_values, + orientation_mode="all", + ) + self.log.info(f"Converted Cartesian position {pose.position} to joint values {joint_values}.") + return joint_values[2:9] + except Exception as e: + self.log.error(f"Error converting Cartesian to joint values: {str(e)}") + raise + + + def calc_ik_error(self, goal_position, goal_orientation): + """ + Calculate the inverse kinematics (IK) error for performing pick-and-place manipulation of an object using a robot arm. + + Args: + goal_position: The desired goal position for the target object. + goal_orientation: The desired goal orientation for the target object. + """ + pass + + # ---------------------------------------------------------------- + # Functions for gripper + # ---------------------------------------------------------------- + + def find_gripper_xarm(self): + """ + Searches for the gripper on available serial ports and returns the port if found. + + Returns: + str: The serial port where the gripper is connected, or None if not found. + """ + _pos = self.robot.get_gripper_position() + _ver = self.robot.get_gripper_version() + + if _ver is not None and _pos is not None: + print("Have Xarm gripper", _ver) + return True + else: + print("Not found Xarm gripper") + return None + + def find_gripper_robotiq(self): + """ + Config the parameter via Python SDK + """ + # Baud rate + # Modify the baud rate to 115200, the default is 2000000. + self.robot.set_tgpio_modbus_baudrate(115200) + + # TCP Payload and offset + # Robotiq 2F/85 Gripper + # self.robot.set_tcp_load(0.925, [0, 0, 58]) + # self.robot.set_tcp_offset([0, 0, 174, 0, 0, 0]) + + + # Self-Collision Prevention Model + # Robotiq 2F/85 Gripper + self.robot.set_collision_tool_model(4) + + self.robot.robotiq_reset() + self.robot.robotiq_set_activate() # enable the robotiq gripper + + def get_gripper_pos_xarm(self): + """ + Get the position of the XArm gripper. + """ + gripper_pos = self.robot.get_gripper_position() + + return gripper_pos[1] + + def set_gripper_pos_xarm(self, value, speed=5000, force=None): + """ + Moves the gripper to a specified position with given speed. + + Args: + value (int): Position of the gripper. Integer between 0 and 800. + 0 represents the open position, and 255 represents the closed position. + speed (int): Speed of the gripper movement, between 0 and 8000. + force (int): Not applicable for xarm gripper + + Note: + - 0 means fully closed. + - 800 means fully open. + """ + self.robot.set_gripper_position( + pos=value, speed=speed, wait=False, timeout=1, auto_enable=True + ) + + def open_gripper_xarm(self): + """Opens the gripper to its maximum position with maximum speed and force.""" + self.gripper_goto(value=850, speed=5000, force=None) + + def get_gripper_pos_robotiq(self, number_of_registers=3): + # TODO + """ + Reading the status of robotiq gripper + + :param number_of_registers: number of registers, 1/2/3, default is 3 + number_of_registers=1: reading the content of register 0x07D0 + number_of_registers=2: reading the content of register 0x07D0/0x07D1 + number_of_registers=3: reading the content of register 0x07D0/0x07D1/0x07D2 + + Note: + register 0x07D0: Register GRIPPER STATUS + register 0x07D1: Register FAULT STATUS and register POSITION REQUEST ECHO + register 0x07D2: Register POSITION and register CURRENT + :return: tuple((code, robotiq_response)) + code: See the [API Code Documentation](./xarm_api_code.md#api-code) for details. + robotiq_response: See the robotiq documentation + """ + status = self.robot.robotiq_get_status(number_of_registers=number_of_registers) + gripper_width = status[1][-2] + return gripper_width + + def set_gripper_pos_robotiq(self, width, speed=0xFF, force=0xFF, wait=False, timeout=5, **kwargs): + # DONE + """ + Go to the position with determined speed and force. + + :param width: position of the gripper. Integer between 0 and 255. 0 being the open position and 255 being the close position. + :param speed: gripper speed between 0 and 255 + :param force: gripper force between 0 and 255 + :param wait: whether to wait for the robotion motion complete, default is True + :param timeout: maximum waiting time(unit: second), default is 5, only available if wait=True + + :return: tuple((code, robotiq_response)) + code: See the [API Code Documentation](./xarm_api_code.md#api-code) for details. + robotiq_response: See the robotiq documentation + """ + return self.robot.robotiq_set_position(width, speed=speed, force=force, wait=wait, timeout=timeout, **kwargs) + + def close_gripper_xarm(self): + """Closes the gripper to its minimum position with maximum speed and force.""" + self.gripper_goto(value=0, speed=5000, force=None) + + def open_gripper_robotiq( + self, speed=0xFF, force=0xFF, wait=False, timeout=5, **kwargs + ): + # DONE + """ + Open the robotiq gripper + + :param speed: gripper speed between 0 and 255 + :param force: gripper force between 0 and 255 + :param wait: whether to wait for the robotiq motion to complete, default is True + :param timeout: maximum waiting time(unit: second), default is 5, only available if wait=True + + :return: tuple((code, robotiq_response)) + code: See the [API Code Documentation](./xarm_api_code.md#api-code) for details. + robotiq_response: See the robotiq documentation + """ + return self.robot.robotiq_open( + speed=speed, force=force, wait=wait, timeout=timeout, **kwargs + ) + + def close_gripper_robotiq( + self, speed=0xFF, force=0xFF, wait=False, timeout=5, **kwargs + ): + # DONE + """ + Close the robotiq gripper + + :param speed: gripper speed between 0 and 255 + :param force: gripper force between 0 and 255 + :param wait: whether to wait for the robotiq motion to complete, default is True + :param timeout: maximum waiting time(unit: second), default is 3, only available if wait=True + + :return: tuple((code, robotiq_response)) + code: See the [API Code Documentation](./xarm_api_code.md#api-code) for details. + robotiq_response: See the robotiq documentation + """ + return self.robot.robotiq_close( + speed=speed, force=force, wait=wait, timeout=timeout, **kwargs + ) diff --git a/Robotics_API/Bestman_Real_Xarm7.py b/Robotics_API/Bestman_Real_Xarm7.py new file mode 100644 index 0000000..e86371b --- /dev/null +++ b/Robotics_API/Bestman_Real_Xarm7.py @@ -0,0 +1,85 @@ +# !/usr/bin/env python +# -*- coding: utf-8 -*- +""" +# @FileName : Bestman_real_xarm7.py +# @Description : Minimal extension for xArm7 based on Bestman_Real_Xarm6 +""" + +import os +from .Bestman_Real_Xarm6 import Bestman_Real_Xarm6 +from ikpy.link import URDFLink +from scipy.spatial.transform import Rotation as R + +class Bestman_Real_Xarm7(Bestman_Real_Xarm6): + def __init__(self, robot_ip, local_ip=None, frequency=None): + # Call parent constructor + super().__init__(robot_ip, local_ip, frequency) + + # Override: Load xArm7 URDF + # current_dir = os.path.dirname(os.path.abspath(__file__)) + + # urdf_file = os.path.join(current_dir, "../Asset/xarm7_robot.urdf") + # if not os.path.exists(urdf_file): + # raise FileNotFoundError(f"URDF file not found: {urdf_file}") + # self.robot_chain = self.robot_chain.__class__.from_urdf_file(urdf_file, base_elements=["world"]) + + # Set active joints mask for 7-DOF + # active_links_mask = [False] + [False] + [True] * 7 + # self.robot_chain.active_links_mask = active_links_mask + + # # Update active joints + # self.active_joints = [ + # joint + # for joint in self.robot_chain.links + # if isinstance(joint, URDFLink) + # and (joint.joint_type == "revolute" or joint.joint_type == "prismatic") + # ] + + # Validate DOF + # if len(self.active_joints) != 7: + # raise ValueError( + # f"Expected 7 active joints, but found {len(self.active_joints)}. " + # f"Check the URDF file and active_links_mask." + # ) + # Override Dofs! + def update_robot_states(self): + """ + Fetches and returns the current robot states (joint angles and TCP pose in quaternion format) for xArm7. + """ + try: + joint_states = self.robot.get_joint_states(is_radian=True) + joint_values = joint_states[1][0][:7] # xArm7: 7 joints + joint_velocities = joint_states[1][1][:7] # xArm7: 7 velocities + + success, tcp_pose = self.robot.get_position(is_radian=True) + if success != 0: + raise ValueError("Failed to retrieve end-effector pose") + + position = [value / 1000 for value in tcp_pose[:3]] + orientation = R.from_euler('xyz', tcp_pose[3:6]).as_quat() + + robot_states = { + "joint_values": joint_values, + "tcp_pose (quaternion)": { + "position": position, + "orientation": orientation.tolist(), + }, + } + + self.log.info(f"[XArm7] Robot states updated: {robot_states}") + return robot_states + + except Exception as e: + self.log.error(f"[XArm7] Error updating robot states: {str(e)}") + + def get_dof(self): + """Override DOF to 7.""" + dof = 7 + self.log.info(f"Robot DOF: {dof}") + return dof + + def get_joint_bounds(self): + """Override joint limits for 7-DOF.""" + joint_min = [-3.14] * 7 + joint_max = [3.14] * 7 + return list(zip(joint_min, joint_max)) diff --git a/Robotics_API/Pose.py b/Robotics_API/Pose.py new file mode 100644 index 0000000..7635ec6 --- /dev/null +++ b/Robotics_API/Pose.py @@ -0,0 +1,101 @@ +# !/usr/bin/env python +# -*- encoding: utf-8 -*- +""" +# @FileName : Pose.py +# @Time : 2024-08-03 15:08:38 +# @Author : yk +# @Email : yangkui1127@gmail.com +# @Description: : POse +""" + + +import numpy as np +from scipy.spatial.transform import Rotation as R + + +class Pose: + """ + A class representing a 3D pose consisting of position and orientation. + """ + + def __init__(self, position, orientation): + """ + Initialize a new Pose object. + + Args: + position (list / np.ndarray): A list or array of three floats representing the position in 3D space. + orientation (list, tuple, np.ndarray): A list, tuple, or array representing the orientation in 3D space. + It can be either Euler angles (3 elements), a quaternion (4 elements), or a 3x3 rotation matrix. + """ + + self.position = list(position) + self.x, self.y, self.z = position + + # Rotation matrix + if isinstance(orientation, np.ndarray) and orientation.shape == (3, 3): + r = R.from_matrix(orientation) + self.orientation = list(r.as_quat()) + # Quaternion + elif ( + isinstance(orientation, (tuple, list, np.ndarray)) and len(orientation) == 4 + ): + self.orientation = list(orientation) + # Euler angles + elif ( + isinstance(orientation, (tuple, list, np.ndarray)) and len(orientation) == 3 + ): + r = R.from_euler("xyz", orientation, degrees=False) + self.orientation = list(r.as_quat()) + else: + raise ValueError( + "[Pose] \033[31merror\033[0m: Orientation input must be Rotation matrix / Quaternion / Euler angles" + ) + + def get_position(self): + """ + get position + + Returns: + list: position + """ + return self.position + + def get_orientation(self, type="quaternion"): + """ + get orientation + + Returns: + list: orientation + """ + if type == "quaternion": + return self.orientation + elif type == "euler": + return R.from_quat(self.orientation).as_euler("xyz", degrees=False) + elif type == "rotation_matrix": + return R.from_quat(self.orientation).as_matrix() + else: + raise ValueError( + "[Pose] \033[31merror\033[0m: Orientation type must be 'quaternion', 'euler', or 'rotation_matrix'" + ) + + def print(self, pose_description="", type="quaternion"): + """ + print pose + """ + if type == "quaternion": + print( + f"[Pose] \033[34mInfo\033[0m: {pose_description} position: [{self.position[0]:.3f}, {self.position[1]:.3f}, {self.position[2]:.3f}], orientation: [{', '.join([f'{x:.3f}' for x in self.get_orientation('quaternion')])}]" + ) + elif type == "euler": + print( + f"[Pose] \033[34mInfo\033[0m: {pose_description} position: [{self.position[0]:.3f}, {self.position[1]:.3f}, {self.position[2]:.3f}], orientation: [{', '.join([f'{x:.3f}' for x in self.get_orientation('euler')])}]" + ) + elif type == "rotation_matrix": + np.set_printoptions(precision=3, suppress=True) + print( + f"[Pose] \033[34mInfo\033[0m: {pose_description} position: [{self.position[0]:.3f}, {self.position[1]:.3f}, {self.position[2]:.3f}], orientation: {np.array2string(self.get_orientation('rotation_matrix'), formatter={'float_kind':lambda x: f'{x:.3f}'})}" + ) + else: + raise ValueError( + "[Pose] \033[31merror\033[0m: Orientation type must be 'quaternion', 'euler', or 'rotation_matrix'" + ) diff --git a/Robotics_API/__init__.py b/Robotics_API/__init__.py new file mode 100644 index 0000000..a2e3d1e --- /dev/null +++ b/Robotics_API/__init__.py @@ -0,0 +1,3 @@ +from .Bestman_Real_Xarm6 import Bestman_Real_Xarm6 +from .Bestman_Real_Xarm7 import Bestman_Real_Xarm7 +from .Pose import Pose \ No newline at end of file diff --git a/Robotics_API/__pycache__/Bestman_Real_Xarm.cpython-38.pyc b/Robotics_API/__pycache__/Bestman_Real_Xarm.cpython-38.pyc new file mode 100644 index 0000000..c28273a Binary files /dev/null and b/Robotics_API/__pycache__/Bestman_Real_Xarm.cpython-38.pyc differ diff --git a/Robotics_API/__pycache__/Bestman_Real_Xarm6.cpython-310.pyc b/Robotics_API/__pycache__/Bestman_Real_Xarm6.cpython-310.pyc new file mode 100644 index 0000000..0934c11 Binary files /dev/null and b/Robotics_API/__pycache__/Bestman_Real_Xarm6.cpython-310.pyc differ diff --git a/Robotics_API/__pycache__/Bestman_Real_Xarm6.cpython-38.pyc b/Robotics_API/__pycache__/Bestman_Real_Xarm6.cpython-38.pyc new file mode 100644 index 0000000..4dc7b57 Binary files /dev/null and b/Robotics_API/__pycache__/Bestman_Real_Xarm6.cpython-38.pyc differ diff --git a/Robotics_API/__pycache__/Bestman_Real_Xarm6.cpython-39.pyc b/Robotics_API/__pycache__/Bestman_Real_Xarm6.cpython-39.pyc new file mode 100644 index 0000000..0f78ab7 Binary files /dev/null and b/Robotics_API/__pycache__/Bestman_Real_Xarm6.cpython-39.pyc differ diff --git a/Robotics_API/__pycache__/Bestman_Real_Xarm7.cpython-310.pyc b/Robotics_API/__pycache__/Bestman_Real_Xarm7.cpython-310.pyc new file mode 100644 index 0000000..1988416 Binary files /dev/null and b/Robotics_API/__pycache__/Bestman_Real_Xarm7.cpython-310.pyc differ diff --git a/Robotics_API/__pycache__/Bestman_Real_Xarm7.cpython-38.pyc b/Robotics_API/__pycache__/Bestman_Real_Xarm7.cpython-38.pyc new file mode 100644 index 0000000..2974411 Binary files /dev/null and b/Robotics_API/__pycache__/Bestman_Real_Xarm7.cpython-38.pyc differ diff --git a/Robotics_API/__pycache__/Bestman_Real_Xarm7.cpython-39.pyc b/Robotics_API/__pycache__/Bestman_Real_Xarm7.cpython-39.pyc new file mode 100644 index 0000000..aa34832 Binary files /dev/null and b/Robotics_API/__pycache__/Bestman_Real_Xarm7.cpython-39.pyc differ diff --git a/Robotics_API/__pycache__/Pose.cpython-310.pyc b/Robotics_API/__pycache__/Pose.cpython-310.pyc new file mode 100644 index 0000000..68bf5d5 Binary files /dev/null and b/Robotics_API/__pycache__/Pose.cpython-310.pyc differ diff --git a/Robotics_API/__pycache__/Pose.cpython-38.pyc b/Robotics_API/__pycache__/Pose.cpython-38.pyc new file mode 100644 index 0000000..245b6f0 Binary files /dev/null and b/Robotics_API/__pycache__/Pose.cpython-38.pyc differ diff --git a/Robotics_API/__pycache__/Pose.cpython-39.pyc b/Robotics_API/__pycache__/Pose.cpython-39.pyc new file mode 100644 index 0000000..a6df98f Binary files /dev/null and b/Robotics_API/__pycache__/Pose.cpython-39.pyc differ diff --git a/Robotics_API/__pycache__/__init__.cpython-310.pyc b/Robotics_API/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..c205a5b Binary files /dev/null and b/Robotics_API/__pycache__/__init__.cpython-310.pyc differ diff --git a/Robotics_API/__pycache__/__init__.cpython-38.pyc b/Robotics_API/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..32c2ba5 Binary files /dev/null and b/Robotics_API/__pycache__/__init__.cpython-38.pyc differ diff --git a/Robotics_API/__pycache__/__init__.cpython-39.pyc b/Robotics_API/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..09a057a Binary files /dev/null and b/Robotics_API/__pycache__/__init__.cpython-39.pyc differ diff --git a/Sensor/Camera.py b/Sensor/Camera.py new file mode 100755 index 0000000..e1aef5e --- /dev/null +++ b/Sensor/Camera.py @@ -0,0 +1,240 @@ +# !/usr/bin/env python +# -*- encoding: utf-8 -*- +""" +# @FileName : Camera.py +# @Time : 2024-12-02 01:45:01 +# @Author : Yan +# @Email : yding25@binghamton.edu +# @Description : Camera functions +# @Usage : None +""" + + +import cv2 +import numpy as np +import pyrealsense2 as rs +from typing import Dict +import time +import threading +import signal + +class Camera: + def __init__(self, device_id: str, width: int = 640, height: int = 480, fps: int = 30): + """ + Initializes the Camera class with RealSense device parameters. + + Args: + device_id (str): The ID of the RealSense device. + width (int): The width of the image stream (default is 640). + height (int): The height of the image stream (default is 480). + fps (int): Frames per second for the image stream (default is 30). + """ + self.device_id = device_id + self.width = width + self.height = height + self.fps = fps + + # Check if a RealSense device is connected + if not self.check_realsense_connection(): + raise RuntimeError("No RealSense device connected.") + + # Configure RealSense pipeline + self.pipeline = rs.pipeline() + self.config = rs.config() + self.config.enable_device(self.device_id) + self.config.enable_stream(rs.stream.color, self.width, self.height, rs.format.bgr8, self.fps) + self.config.enable_stream(rs.stream.color, 640, 480, rs.format.bgr8, 30) + self.config.enable_stream(rs.stream.depth, 640, 480, rs.format.z16, 30) + + # Start streaming + self.profile = self.pipeline.start(self.config) + time.sleep(2) # Wait for the camera to stabilize + + # Get the stream profile and camera intrinsics + stream = self.profile.get_stream(rs.stream.color) + + # Debug: Extract camera intrinsic parameters + intrinsics = stream.as_video_stream_profile().get_intrinsics() + self.camera_matrix_test = np.array([[intrinsics.fx, 0, intrinsics.ppx], + [0, intrinsics.fy, intrinsics.ppy], + [0, 0, 1]], dtype=float) + print(f'self.camera_matrix_test:{self.camera_matrix_test}') + + #TODO: camera matrix is wrong + self.camera_matrix = np.array([[607.168, 0, 325.575], [0, 606.94, 325.575], [0, 0, 1]], dtype=float) + self.dist_coeffs = np.zeros((4, 1)) # Assuming no lens distortion + + # Load the predefined ArUco marker dictionary + self.aruco_dict = cv2.aruco.getPredefinedDictionary(cv2.aruco.DICT_6X6_250) + self.parameters = cv2.aruco.DetectorParameters() + + # check realsense status + def check_realsense_connection(self) -> bool: + """ + Checks if any RealSense device is connected. + + Returns: + bool: True if a device is detected, False otherwise. + """ + context = rs.context() + if len(context.devices) == 0: + print("No RealSense devices connected.") + return False + else: + print("RealSense device detected.") + return True + + def update(self): + """ + Captures the latest color frame from the camera. + + Returns: + np.ndarray: The color frame as a NumPy array, or None if not available. + """ + frames = self.pipeline.wait_for_frames() + color_frame = frames.get_color_frame() + if not color_frame: + return None + + color_image = np.asanyarray(color_frame.get_data()) + return color_image + + def get_marker_positions(self, debug: bool = False) -> np.ndarray: + """ + Detects ArUco markers in the camera's field of view and returns their positions. + + Args: + debug (bool): If True, displays the frame with markers for debugging. + + Returns: + np.ndarray: An array of marker positions in the format [y, x, -z]. + """ + try: + while True: + color_image = self.update() + if color_image is None: + continue + + # Convert to grayscale + gray = cv2.cvtColor(color_image, cv2.COLOR_BGR2GRAY) + + # Detect ArUco markers in the image + corners, ids, _ = cv2.aruco.detectMarkers(gray, self.aruco_dict, parameters=self.parameters) + + marker_positions: Dict[int, np.ndarray] = {} + + # If markers are detected + if ids is not None: + # Draw the detected markers + cv2.aruco.drawDetectedMarkers(color_image, corners, ids) + + for i in range(len(ids)): + rvec, tvec, _ = cv2.aruco.estimatePoseSingleMarkers(corners[i], 0.035, self.camera_matrix, self.dist_coeffs) + # Draw the axis for each marker + if hasattr(cv2.aruco, 'drawAxis'): + cv2.aruco.drawAxis(color_image, self.camera_matrix, self.dist_coeffs, rvec, tvec, 0.1) + # Store the position in a dictionary + marker_positions[ids[i][0]] = tvec[0][0] + + # Convert the positions dictionary to a NumPy array + positions_array = np.array([[pos[1], pos[0], -pos[2]] for pos in marker_positions.values()]) + + # Debug mode: Show the frame with markers + if debug: + cv2.imshow('RealSense Debug', color_image) + if cv2.waitKey(1000) & 0xFF == ord('q'): + break + + return positions_array + + # In debug mode, show the frame even if no markers are detected + if debug: + cv2.imshow('RealSense Debug', color_image) + if cv2.waitKey(1000) & 0xFF == ord('q'): + break + + return None + finally: + # Stop streaming + self.pipeline.stop() + if debug: + cv2.destroyAllWindows() + + + def display(self, option="rgb"): + """ + Displays frames from the RealSense camera in a separate thread. + Supports RGB, depth, or both. + + Parameters: + option (str): Determines the display mode. + "rgb" - display only RGB frames. + "d" - display only depth frames. + "rgbd" - display both RGB and depth frames. + + This function listens for the Ctrl+C signal to stop gracefully. + """ + stop_flag = [False] # Mutable flag to control the thread termination + + def signal_handler(sig, frame): + """ + Signal handler for gracefully exiting the program on Ctrl+C. + + Args: + sig: Signal number. + frame: Current stack frame. + """ + stop_flag[0] = True + print("Ctrl+C detected. Exiting...") + + # Bind the SIGINT signal (Ctrl+C) to the signal handler + signal.signal(signal.SIGINT, signal_handler) + + def process_frames(): + """ + Fetches frames from the RealSense camera and displays them + in a loop until the stop_flag is set to True. + """ + try: + while not stop_flag[0]: + # Fetch frames non-blocking + frames = self.pipeline.poll_for_frames() + if not frames: + continue + + # Retrieve color and depth frames based on the selected option + color_frame = frames.get_color_frame() if option in ["rgb", "rgbd"] else None + depth_frame = frames.get_depth_frame() if option in ["d", "rgbd"] else None + + # Skip iteration if frames are not available for the selected mode + if option in ["rgb", "rgbd"] and not color_frame: + continue + if option in ["d", "rgbd"] and not depth_frame: + continue + + # Display RGB image if available + if color_frame: + color_image = np.asanyarray(color_frame.get_data()) + cv2.imshow('RGB Frame', color_image) + + # Display depth image if available + if depth_frame: + depth_image = np.asanyarray(depth_frame.get_data()) + # Normalize depth data and apply color mapping for visualization + depth_colormap = cv2.convertScaleAbs(depth_image, alpha=255.0 / depth_image.max()) + cv2.imshow('Depth Frame', depth_colormap) + + # Exit loop if 'q' key is pressed + if cv2.waitKey(1) & 0xFF == ord('q'): + break + finally: + # Ensure resources are properly released + self.pipeline.stop() # Stop the RealSense pipeline + cv2.destroyAllWindows() # Close OpenCV display windows + + # Start the frame processing in a separate thread + thread = threading.Thread(target=process_frames) + thread.start() + + # Wait for the thread to finish (main thread blocks here) + thread.join() diff --git a/Sensor/__init__.py b/Sensor/__init__.py new file mode 100644 index 0000000..67815df --- /dev/null +++ b/Sensor/__init__.py @@ -0,0 +1,3 @@ +from .Camera import Camera + +__all__ = ["Camera"] \ No newline at end of file diff --git a/Sensor/__pycache__/Camera.cpython-38.pyc b/Sensor/__pycache__/Camera.cpython-38.pyc new file mode 100644 index 0000000..735cccb Binary files /dev/null and b/Sensor/__pycache__/Camera.cpython-38.pyc differ diff --git a/Sensor/__pycache__/__init__.cpython-38.pyc b/Sensor/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..195bd75 Binary files /dev/null and b/Sensor/__pycache__/__init__.cpython-38.pyc differ diff --git a/Sensor/__pycache__/camera.cpython-38.pyc b/Sensor/__pycache__/camera.cpython-38.pyc new file mode 100644 index 0000000..7624df0 Binary files /dev/null and b/Sensor/__pycache__/camera.cpython-38.pyc differ diff --git a/Sensor/force.py b/Sensor/force.py new file mode 100644 index 0000000..9408046 --- /dev/null +++ b/Sensor/force.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# Software License Agreement (BSD License) +# +# Copyright (c) 2020, UFACTORY, Inc. +# All rights reserved. +# +# Author: Vinman + +""" +Description: read force raw data and external data +""" + +import sys +import time +import math +import socket +import struct +from xarm.wrapper import XArmAPI +####################################################### + +if len(sys.argv) >= 2: + ip = sys.argv[1] +else: + try: + from configparser import ConfigParser + parser = ConfigParser() + parser.read('../robot.conf') + ip = parser.get('xArm', 'ip') + except: + ip = input('Please input the xArm ip address:') + if not ip: + print('input error, exit') + sys.exit(1) +######################################################## + +arm = XArmAPI(ip, enable_report=True) +arm.motion_enable(enable=True) +arm.ft_sensor_enable(0) + +arm.clean_error() +arm.clean_warn() +arm.ft_sensor_enable(1) +time.sleep(0.5) +arm.ft_sensor_set_zero() + +while arm.connected and arm.error_code == 0: + # ft_raw_force and ft_ext_force will update by reporting socket + # print('raw_force: {}'.format(arm.ft_raw_force)) + print('exe_force: {}'.format(arm.ft_ext_force)) + + # # get_ft_sensor_data() will get the last ext_force + # code, ext_force = arm.get_ft_sensor_data() + # if code == 0: + # print('exe_force: {}'.format(ext_force)) + time.sleep(0.2) + +arm.ft_sensor_enable(0) +arm.disconnect() \ No newline at end of file diff --git a/TODO/Utils.py b/TODO/Utils.py new file mode 100644 index 0000000..8bd4bac --- /dev/null +++ b/TODO/Utils.py @@ -0,0 +1,10 @@ +# !/usr/bin/env python +# -*- encoding: utf-8 -*- +""" +# @FileName : utils.py +# @Time : 2024-12-02 01:01:41 +# @Author : Yan +# @Email : yding25@binghamton.edu +# @Description : Utils +# @Usage : None +""" \ No newline at end of file