33import threading
44import time
55from dataclasses import dataclass
6- from functools import partial
7- from typing import Any , Callable , Dict , List , Optional , Union
6+ from typing import Any , Dict , List , Optional , Union
87
98import numpy as np
109
1514 ReceiveMode ,
1615)
1716from i2rt .robots .robot import Robot
18- from i2rt .robots .utils import JointMapper
17+ from i2rt .robots .utils import GripperForceLimiter , JointMapper
1918from i2rt .utils .mujoco_utils import MuJoCoKDL
2019
2120I2RT_ROOT = os .path .dirname (os .path .dirname (os .path .dirname (os .path .abspath (__file__ ))))
22- YAM_XML_PATH = os .path .join (I2RT_ROOT , "robot_models/yam_v0/mjmodel.xml" )
23- ARX_XML_PATH = os .path .join (I2RT_ROOT , "robot_models/arx_r5/mjmodel.xml" )
24-
25-
26- def sigmoid (x : float ) -> float :
27- return 1 / (1 + np .exp (- x ))
28-
29-
30- def sigmoid_with_full_shift (x : float , w : float = 1 , b_x : float = 0 , b_y : float = 0 ) -> float :
31- return sigmoid (w * (x - b_x )) + b_y
32-
33-
34- MAX_ALLOWED_GRIPPER_OFFSET_RATIO = (
35- 0.02 # when kp is 20 and the gripper range in 5, the max gripper torque is around 0.02 * 20 * 5 = 2.
36- )
37-
21+ YAM_XML_PATH = os .path .join (I2RT_ROOT , "robot_models/yam/yam.urdf" )
22+ ARX_XML_PATH = os .path .join (I2RT_ROOT , "robot_models/arx_r5/arx.urdf" )
3823
3924@dataclass
4025class JointStates :
@@ -74,101 +59,6 @@ def init_all_zero(cls, n_joints: int) -> "JointCommands":
7459 )
7560
7661
77- # todo: implement real gripper finger tip level force limit
78- def linear_gripper_force_torque_map (
79- motor_stroke : float , gripper_stroke : float , gripper_force : float , current_angle : float
80- ) -> float :
81- """Maps the motor stroke required to achieve a given gripper force.
82-
83- Args:
84- motor_stroke (float): in rad
85- gripper_stroke (float): in meter
86- gripper_force (float): in newton
87- """
88- # force = torque * motor_stroke / gripper_stroke
89- return gripper_force * motor_stroke / gripper_stroke
90-
91-
92- def zero_linkage_crank_gripper_force_torque_map (
93- gripper_close_angle : float ,
94- gripper_open_angle : float ,
95- motor_reading_to_crank_angle : Callable [[float ], float ],
96- gripper_stroke : float ,
97- current_angle : float ,
98- gripper_force : float ,
99- ) -> float :
100- """Maps the motor crank torque required to achieve a given gripper force.
101-
102- Args:
103- gripper_close_angle (float): Angle of the crank in radians at the closed position.
104- gripper_open_angle (float): Angle of the crank in radians at the open position.
105- gripper_stroke (float): Linear displacement of the gripper in meters.
106- current_angle (float): Current crank angle in radians (relative to the closed position).
107- gripper_force (float): Required gripping force in Newtons (N).
108-
109- Returns:
110- float: Required motor torque in Newton-meters (Nm).
111- """
112- current_angle = motor_reading_to_crank_angle (current_angle )
113- # Compute crank radius based on the total stroke and angle change
114- crank_radius = gripper_stroke / (2 * (np .cos (gripper_close_angle ) - np .cos (gripper_open_angle )))
115- # gripper_position = crank_radius * (np.cos(gripper_close_angle) - np.cos(current_angle + gripper_close_angle))
116- grad_gripper_position = crank_radius * np .sin (current_angle )
117-
118- # Compute the required torque
119- target_torque = gripper_force * grad_gripper_position
120- return target_torque
121-
122-
123- class GripperForceLimiter :
124- def __init__ (self , max_force : float , gripper_type : str ):
125- self .max_force = max_force
126- self .gripper_type = gripper_type
127- self ._is_clogged = False
128- if self .gripper_type == "arx_92mm_linear" :
129- self .gripper_force_torque_map = partial (
130- linear_gripper_force_torque_map ,
131- motor_stroke = 4.93 ,
132- gripper_stroke = 0.092 ,
133- gripper_force = self .max_force ,
134- )
135- elif self .gripper_type == "yam_small" :
136- self .gripper_force_torque_map = partial (
137- zero_linkage_crank_gripper_force_torque_map ,
138- motor_reading_to_crank_angle = lambda x : (- x + 0.174 ),
139- gripper_close_angle = 8 / 180.0 * np .pi ,
140- gripper_open_angle = 170 / 180.0 * np .pi ,
141- gripper_stroke = 0.071 , # unit in meter
142- gripper_force = self .max_force ,
143- )
144- else :
145- raise ValueError (f"Unknown gripper type: { self .gripper_type } " )
146-
147- def compute_target_gripper_torque (self , gripper_state : Dict [str , float ]) -> float :
148- current_eff = gripper_state ["current_eff" ]
149- current_speed = gripper_state ["current_qvel" ]
150- if self ._is_clogged :
151- normalized_current_qpos = gripper_state ["current_normalized_qpos" ]
152- normalized_target_qpos = gripper_state ["target_normalized_qpos" ]
153- # 0 close 1 open
154- if normalized_current_qpos < normalized_target_qpos : # want to open
155- self ._is_clogged = False
156- # code below will cause oscillation on some configurations
157- # if np.abs(current_eff) < 0.3 and np.abs(current_speed) > 0.35:
158- # self._is_clogged = False
159- else :
160- if np .abs (current_eff ) > 0.4 and np .abs (current_speed ) < 0.3 :
161- self ._is_clogged = True
162-
163- if self ._is_clogged :
164- target_eff = self .gripper_force_torque_map (current_angle = gripper_state ["current_qpos" ])
165- # print(f"current_eff: {current_eff}, gripper clogged, adjust force to {target_eff}")
166- self ._is_clogged = True
167- return target_eff + 0.3 # this is to compensate the friction
168- else :
169- return None
170-
171-
17262class MotorChainRobot (Robot ):
17363 """A generic Robot protocol."""
17464
@@ -190,16 +80,13 @@ def __init__(
19080 ) -> None :
19181 if gripper_index is not None :
19282 assert gripper_limits is not None , "Gripper limits are required if gripper index is provided."
193- self ._max_gripper_offset = abs (gripper_limits [1 ] - gripper_limits [0 ]) * MAX_ALLOWED_GRIPPER_OFFSET_RATIO
194- else :
195- self ._max_gripper_offset = None
19683
19784 if joint_limits is not None :
19885 joint_limits = np .array (joint_limits )
19986 assert np .all (
20087 joint_limits [:, 0 ] < joint_limits [:, 1 ]
20188 ), "Lower joint limits must be smaller than upper limits"
202-
89+ self . _last_gripper_command_qpos = None
20390 self ._joint_limits = joint_limits
20491 assert clip_motor_torque >= 0.0
20592 self ._clip_motor_torque = clip_motor_torque
@@ -211,15 +98,12 @@ def __init__(
21198 self ._gripper_index = gripper_index
21299 self .remapper = JointMapper ({}, len (motor_chain )) # so it works without gripper
213100 self ._gripper_limits = gripper_limits
214- self ._gripper_force_limiter = GripperForceLimiter (
215- max_force = limit_gripper_force , gripper_type = gripper_type
216- ) # force in newton
217101 self ._gripper_adjusted_qpos = None
218102 if self ._gripper_index is not None :
103+ self ._gripper_force_limiter = GripperForceLimiter (
104+ max_force = limit_gripper_force , gripper_type = gripper_type , kp = kp [gripper_index ]
105+ ) # force in newton
219106 self ._limit_gripper_force = limit_gripper_force
220- self ._max_gripper_offset = (
221- abs (self ._gripper_limits [1 ] - self ._gripper_limits [0 ]) * MAX_ALLOWED_GRIPPER_OFFSET_RATIO
222- )
223107
224108 self .remapper = JointMapper (
225109 index_range_map = {gripper_index : gripper_limits },
@@ -335,36 +219,19 @@ def update(self) -> None:
335219 "target_normalized_qpos" : self .remapper .to_command_joint_pos_space (joint_commands .pos )[
336220 self ._gripper_index
337221 ],
222+ "last_command_qpos" : self ._last_gripper_command_qpos ,
338223 }
339224
340- target_eff = self ._gripper_force_limiter .compute_target_gripper_torque (gripper_state )
341- if target_eff is not None :
342- command_sign = np .sign (gripper_state ["target_qpos" ] - gripper_state ["current_qpos" ])
343-
344- current_zero_eff_pos = (
345- self ._gripper_adjusted_qpos
346- - command_sign * gripper_state ["current_eff" ] / self ._kp [self ._gripper_index ]
347- )
348-
349- target_gripper_raw_pos = (
350- current_zero_eff_pos + command_sign * target_eff / self ._kp [self ._gripper_index ]
351- )
352-
353- # Update gripper target position
354- a = 0.1
355- self ._gripper_adjusted_qpos = (
356- 1 - a
357- ) * self ._gripper_adjusted_qpos + a * target_gripper_raw_pos
358- joint_commands .pos [self ._gripper_index ] = self ._gripper_adjusted_qpos
359- else :
360- self ._gripper_adjusted_qpos = gripper_state ["current_qpos" ]
225+ joint_commands .pos [self ._gripper_index ] = self ._gripper_force_limiter .update (gripper_state )
361226
362227 # add final clip so the gripper won't be over-adjusted
363- joint_commands .pos [self ._gripper_index ] = np .clip (
364- joint_commands .pos [self ._gripper_index ],
365- min (self ._gripper_limits ),
366- max (self ._gripper_limits ),
367- )
228+ joint_commands .pos [self ._gripper_index ] = np .clip (
229+ joint_commands .pos [self ._gripper_index ],
230+ min (self ._gripper_limits ),
231+ max (self ._gripper_limits ),
232+ )
233+ self ._last_gripper_command_qpos = joint_commands .pos [self ._gripper_index ]
234+
368235 # Send commands to motor chain and update joint state
369236 motor_state = self .motor_chain .set_commands (
370237 motor_torques ,
@@ -587,7 +454,7 @@ def get_arx_robot(channel: str = "can0", model_path: str = ARX_XML_PATH) -> Moto
587454 gripper_limits = np .array ([0.0 , 4.93 ]),
588455 kp = np .array ([80 , 80 , 80 , 40 , 10 , 10 , 20 ]),
589456 kd = np .array ([5 , 5 , 5 , 1.5 , 1.5 , 1.5 , 0.5 ]),
590- limit_gripper_force = 50 .0 ,
457+ limit_gripper_force = 20 .0 ,
591458 gripper_type = "arx_92mm_linear" ,
592459 )
593460
@@ -616,7 +483,8 @@ def get_arx_robot(channel: str = "can0", model_path: str = ARX_XML_PATH) -> Moto
616483 time .sleep (1 )
617484 elif args .operation_mode == "test_gripper" :
618485 for _ in range (30 ):
619- for gripper_pos in [1.0 , 0.3 ]:
486+ for gripper_pos in [0.8 , 0.0 ]:
487+ print (f"gripper_pos: { gripper_pos } " )
620488 robot .command_joint_pos (np .array ([0.0 , 0.0 , 0.0 , 0.0 , 0.0 , 0.0 , gripper_pos ]))
621- time .sleep (5 )
489+ time .sleep (4 )
622490 print (robot .get_observations ())
0 commit comments