Files
lerobot/lerobot/bi_teleoperate.py
T

139 lines
4.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import math
import time
## Pseudo code for Impedance control and 4 channel bilateral tele-op
# 1. Read the leader and follower
# 2. Compute the follower's impedance torque
# 3. Compute the leader's torque
# 4. Set the leader and follower's torque
# # Read leader
# θ_, ω_, F_ = leader.read_state()
# # Read the follower
# θ_f, ω_f, F_f = follower.read_state()
# # Compute the followers impedance torque
# τ_f = (
# Kp*(θ_ - θ_f) # position coupling
# + Kd*(ω_ - ω_f) # velocity coupling
# + K_ff * F_ # force feed-forward from the leader
# )
# follower.set_torque(τ_f)
# # Compute the leaders torque
# τ_ = (
# K_mp*(θ_f - θ_) # motion-feedback
# + K_md*(ω_f - ω_) # velocity-feedback
# - K_mf * F_f # force-feedback (negative)
# )
# leader.set_torque(τ_)
# Other details mentioned by Bi-ACT paper:
# The controller design adopted a control of position and force
# for each axis, as shown in Fig. 3. Angle information was
# obtained from encoders, and angular velocity was calculated
# by differentiating this information. The disturbance torque τdis
# was calculated using a disturbance observer (DOB) [29], and
# the torque response value τres was estimated using a force
# reaction observer (RFOB) [30].
from lerobot.common.robots.so101_follower_torque.config_so101_follower_t import SO101FollowerTConfig
from lerobot.common.robots.so101_follower_torque.so101_follower_t import SO101FollowerT
F_C = 0.2 # Nm, coulomb (static)
F_V = 0.05 # Nm·s rad⁻¹, residual viscous
Kt = 1.4043083213990768 # Nm/A
AMP_PER_LSB = 0.0065 # 6.5 mA/LSB
MAX_LSB = 2047
Kp, Kd, K_local = 15, 0.5, 0.01
DT = 0.002 # 500 Hz
MOTORS = ["shoulder_pan", "shoulder_lift", "elbow_flex", "wrist_flex", "wrist_roll"] # "gripper"
TICKS_TO_RAD = 2.0 * math.pi / 4096.0 # Feetech HLS-3625: 4096 ticks = 1 rev
STEP = 0
SCALE_TRANSIENT = 0.2
SCALE_NORMAL = 1.0
def friction(omega: float) -> float:
return F_C * math.tanh(omega / 0.05) + F_V * omega
def nm_to_lsb(tau: float) -> int:
return int(max(-MAX_LSB, min(MAX_LSB, round((tau / Kt) / AMP_PER_LSB))))
def ticks_to_rad(ticks: int) -> float:
return ticks * TICKS_TO_RAD
robot_config = SO101FollowerTConfig(
port="/dev/tty.usbmodem58760431551",
id="my_awesome_follower_arm",
)
teleop_config = SO101FollowerTConfig(
port="/dev/tty.usbmodem58760428721",
id="my_awesome_leader_arm",
)
robot = SO101FollowerT(robot_config)
teleop_device = SO101FollowerT(teleop_config)
robot.connect()
teleop_device.connect()
print("Starting bilateral tele-op")
prev_l = dict.fromkeys(MOTORS, 0.0)
prev_f = dict.fromkeys(MOTORS, 0.0)
last_print = time.time()
loops = 0
while True:
STEP += 1
# first 1000 steps at 20%, then full effort
scale = SCALE_TRANSIENT if STEP <= 1000 else SCALE_NORMAL
tic = time.perf_counter()
obs_l = robot.get_observation()
obs_f = teleop_device.get_observation()
tgt_l, tgt_f = {}, {}
for j in MOTORS:
ticks_l = obs_l[f"{j}.pos"]
ticks_f = obs_f[f"{j}.pos"]
th_l = ticks_to_rad(ticks_l) # → radians
th_f = ticks_to_rad(ticks_f)
dth_l = (th_l - prev_l[j]) / DT
dth_f = (th_f - prev_f[j]) / DT
prev_l[j], prev_f[j] = th_l, th_f
tau_l = -(Kp * (th_f - th_l) + Kd * (dth_f - dth_l) - K_local * dth_l) - friction(dth_l)
tau_f = -(Kp * (th_l - th_f) + Kd * (dth_l - dth_f) - K_local * dth_f) - friction(dth_f)
tau_l *= scale
tau_f *= scale
tgt_l[f"{j}.effort"] = nm_to_lsb(tau_l)
tgt_f[f"{j}.effort"] = nm_to_lsb(tau_f)
robot.send_action(tgt_l)
teleop_device.send_action(tgt_f)
leader_str = " | ".join(f"{j}:{tgt_l[f'{j}.effort']:+5d}" for j in MOTORS)
follower_str = " | ".join(f"{j}:{tgt_f[f'{j}.effort']:+5d}" for j in MOTORS)
print(f"Leader: {leader_str}")
print(f"Follower: {follower_str}")
loops += 1
elapsed = time.perf_counter() - tic
to_sleep = DT - elapsed
if to_sleep > 0:
time.sleep(to_sleep)
now = time.time()
if now - last_print >= 1.0:
print(f"{loops / (now - last_print):6.1f} Hz control loop") # e.g. "500.2 Hz"
loops = 0
last_print = now