forked from YikaiFu-cart/acRealman_xr
Enhance orientation control for RM75 arms in XR teleoperation
This commit is contained in:
@ -32,6 +32,14 @@ AXIS_SWEEP_SEGMENTS = (
|
||||
("XR -Z front", (0.0, 0.0, -1.0), "hold"),
|
||||
("CENTER / Z done", (0.0, 0.0, 0.0), "center"),
|
||||
)
|
||||
RPY_DIRECTIONS = (
|
||||
("XR roll +", (1.0, 0.0, 0.0)),
|
||||
("XR roll -", (-1.0, 0.0, 0.0)),
|
||||
("XR pitch +", (0.0, 1.0, 0.0)),
|
||||
("XR pitch -", (0.0, -1.0, 0.0)),
|
||||
("XR yaw +", (0.0, 0.0, 1.0)),
|
||||
("XR yaw -", (0.0, 0.0, -1.0)),
|
||||
)
|
||||
|
||||
|
||||
def _validate_positive(name: str, value: float) -> float:
|
||||
@ -66,6 +74,12 @@ def _pattern_period(pattern: str, hold_seconds: float, center_seconds: float) ->
|
||||
return 5.0
|
||||
|
||||
|
||||
def _rotation_pattern_period(pattern: str, hold_seconds: float, center_seconds: float) -> float:
|
||||
if pattern == "rpy_steps":
|
||||
return (hold_seconds + center_seconds) * len(RPY_DIRECTIONS)
|
||||
return 0.0
|
||||
|
||||
|
||||
def _axis_sweep_position(
|
||||
t: float,
|
||||
amplitude: float,
|
||||
@ -121,6 +135,66 @@ def _sine_position(t: float, amplitude: float, phase: float) -> tuple[str, list[
|
||||
]
|
||||
|
||||
|
||||
def _euler_to_quaternion(roll: float, pitch: float, yaw: float) -> list[float]:
|
||||
cy = math.cos(yaw * 0.5)
|
||||
sy = math.sin(yaw * 0.5)
|
||||
cp = math.cos(pitch * 0.5)
|
||||
sp = math.sin(pitch * 0.5)
|
||||
cr = math.cos(roll * 0.5)
|
||||
sr = math.sin(roll * 0.5)
|
||||
return [
|
||||
sr * cp * cy - cr * sp * sy,
|
||||
cr * sp * cy + sr * cp * sy,
|
||||
cr * cp * sy - sr * sp * cy,
|
||||
cr * cp * cy + sr * sp * sy,
|
||||
]
|
||||
|
||||
|
||||
def _rpy_steps_quaternion(
|
||||
t: float,
|
||||
amplitude_rad: float,
|
||||
hold_seconds: float,
|
||||
center_seconds: float,
|
||||
initial_center_seconds: float,
|
||||
) -> tuple[str, list[float]]:
|
||||
if t < initial_center_seconds:
|
||||
return "ROT center / lock origin", [0.0, 0.0, 0.0, 1.0]
|
||||
|
||||
segment_seconds = hold_seconds + center_seconds
|
||||
period = segment_seconds * len(RPY_DIRECTIONS)
|
||||
cycle_t = (t - initial_center_seconds) % period
|
||||
direction_index = int(cycle_t // segment_seconds)
|
||||
segment_t = cycle_t - direction_index * segment_seconds
|
||||
label, direction = RPY_DIRECTIONS[direction_index]
|
||||
if segment_t >= hold_seconds:
|
||||
return f"ROT center after {label}", [0.0, 0.0, 0.0, 1.0]
|
||||
|
||||
return label, _euler_to_quaternion(
|
||||
amplitude_rad * direction[0],
|
||||
amplitude_rad * direction[1],
|
||||
amplitude_rad * direction[2],
|
||||
)
|
||||
|
||||
|
||||
def _rotation_quaternion(
|
||||
pattern: str,
|
||||
t: float,
|
||||
amplitude_rad: float,
|
||||
hold_seconds: float,
|
||||
center_seconds: float,
|
||||
initial_center_seconds: float,
|
||||
) -> tuple[str, list[float]]:
|
||||
if pattern == "rpy_steps":
|
||||
return _rpy_steps_quaternion(
|
||||
t,
|
||||
amplitude_rad,
|
||||
hold_seconds,
|
||||
center_seconds,
|
||||
initial_center_seconds,
|
||||
)
|
||||
return "ROT none", [0.0, 0.0, 0.0, 1.0]
|
||||
|
||||
|
||||
def _pattern_position(
|
||||
pattern: str,
|
||||
t: float,
|
||||
@ -186,6 +260,18 @@ def main() -> None:
|
||||
default=0.0,
|
||||
help="发送的 trigger 值。方向调试默认不驱动夹爪。",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--rotation-pattern",
|
||||
choices=("none", "rpy_steps"),
|
||||
default="none",
|
||||
help="可选姿态测试轨迹;none 保持 identity 四元数,rpy_steps 逐轴发送小角度姿态。",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--rotation-amplitude-deg",
|
||||
type=_positive_float("rotation-amplitude-deg"),
|
||||
default=25.0,
|
||||
help="rpy_steps 模式下的姿态幅度,单位 degree。",
|
||||
)
|
||||
parser.add_argument("--hand", choices=("left", "right", "both"), default="right")
|
||||
parser.add_argument(
|
||||
"--both-mode",
|
||||
@ -212,17 +298,22 @@ def main() -> None:
|
||||
}
|
||||
for hand in hands
|
||||
}
|
||||
pattern_period = _pattern_period(args.pattern, args.hold_seconds, args.center_seconds)
|
||||
pattern_period = max(
|
||||
_pattern_period(args.pattern, args.hold_seconds, args.center_seconds),
|
||||
_rotation_pattern_period(args.rotation_pattern, args.hold_seconds, args.center_seconds),
|
||||
)
|
||||
one_hand_seconds = args.initial_center_seconds + pattern_period
|
||||
full_sweep_seconds = one_hand_seconds * len(hands) if args.both_mode == "staggered" else one_hand_seconds
|
||||
rotation_amplitude_rad = math.radians(args.rotation_amplitude_deg)
|
||||
print(
|
||||
"Sample UDP sender: "
|
||||
f"hand={args.hand}, both_mode={args.both_mode}, pattern={args.pattern}, "
|
||||
f"rotation_pattern={args.rotation_pattern}, "
|
||||
f"amplitude={args.amplitude:.3f}m, hold={args.hold_seconds:.2f}s, "
|
||||
f"center={args.center_seconds:.2f}s, seconds={args.seconds:.1f}s",
|
||||
flush=True,
|
||||
)
|
||||
if args.pattern != "sine" and args.seconds < full_sweep_seconds:
|
||||
if (args.pattern != "sine" or args.rotation_pattern != "none") and args.seconds < full_sweep_seconds:
|
||||
print(
|
||||
"提示:当前 seconds 不足以跑完一轮完整方向序列;"
|
||||
f"建议至少 {full_sweep_seconds:.1f}s。",
|
||||
@ -241,8 +332,10 @@ def main() -> None:
|
||||
hand_end_t = hand_start_t + one_hand_seconds
|
||||
if t < hand_start_t:
|
||||
label, pos = "WAIT / center", BASE_POS.copy()
|
||||
rot_label, quat = "ROT wait / center", [0.0, 0.0, 0.0, 1.0]
|
||||
elif t >= hand_end_t:
|
||||
label, pos = "DONE / center", BASE_POS.copy()
|
||||
rot_label, quat = "ROT done / center", [0.0, 0.0, 0.0, 1.0]
|
||||
else:
|
||||
pattern_t = t - hand_start_t
|
||||
label, pos = _pattern_position(
|
||||
@ -254,6 +347,14 @@ def main() -> None:
|
||||
args.initial_center_seconds,
|
||||
index * math.pi,
|
||||
)
|
||||
rot_label, quat = _rotation_quaternion(
|
||||
args.rotation_pattern,
|
||||
pattern_t,
|
||||
rotation_amplitude_rad,
|
||||
args.hold_seconds,
|
||||
args.center_seconds,
|
||||
args.initial_center_seconds,
|
||||
)
|
||||
else:
|
||||
label, pos = _pattern_position(
|
||||
args.pattern,
|
||||
@ -264,15 +365,30 @@ def main() -> None:
|
||||
args.initial_center_seconds,
|
||||
index * math.pi,
|
||||
)
|
||||
rot_label, quat = _rotation_quaternion(
|
||||
args.rotation_pattern,
|
||||
pattern_t,
|
||||
rotation_amplitude_rad,
|
||||
args.hold_seconds,
|
||||
args.center_seconds,
|
||||
args.initial_center_seconds,
|
||||
)
|
||||
|
||||
if label != last_labels[hand]:
|
||||
print(f"[{t:5.2f}s] {hand:<5} {label:<22} pos={pos}", flush=True)
|
||||
last_labels[hand] = label
|
||||
combined_label = label if args.rotation_pattern == "none" else f"{label} | {rot_label}"
|
||||
if combined_label != last_labels[hand]:
|
||||
quat_text = [round(value, 4) for value in quat]
|
||||
print(
|
||||
f"[{t:5.2f}s] {hand:<5} {combined_label:<46} "
|
||||
f"pos={pos} quat={quat_text}",
|
||||
flush=True,
|
||||
)
|
||||
last_labels[hand] = combined_label
|
||||
|
||||
packet["t"] = t
|
||||
packet["grip"] = True
|
||||
packet["trigger"] = min(max(args.trigger, 0.0), 1.0)
|
||||
packet["pos"] = pos
|
||||
packet["quat"] = quat
|
||||
sock.sendto(json.dumps(packet).encode("utf-8"), (args.host, args.port))
|
||||
time.sleep(dt)
|
||||
|
||||
@ -280,6 +396,7 @@ def main() -> None:
|
||||
packet["grip"] = False
|
||||
packet["trigger"] = 0.0
|
||||
packet["pos"] = BASE_POS.copy()
|
||||
packet["quat"] = [0.0, 0.0, 0.0, 1.0]
|
||||
sock.sendto(json.dumps(packet).encode("utf-8"), (args.host, args.port))
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user