Skip to content

Commit 1b488ac

Browse files
Update motor_chain_robot.py
1 parent 7ae6f79 commit 1b488ac

File tree

1 file changed

+21
-153
lines changed

1 file changed

+21
-153
lines changed

i2rt/robots/motor_chain_robot.py

Lines changed: 21 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
import threading
44
import time
55
from 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

98
import numpy as np
109

@@ -15,26 +14,12 @@
1514
ReceiveMode,
1615
)
1716
from i2rt.robots.robot import Robot
18-
from i2rt.robots.utils import JointMapper
17+
from i2rt.robots.utils import GripperForceLimiter, JointMapper
1918
from i2rt.utils.mujoco_utils import MuJoCoKDL
2019

2120
I2RT_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
4025
class 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-
17262
class 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

Comments
 (0)